0X00 前言
这篇文章将会总结一些内核漏洞利用的相关知识 因为版本在不断的提升 所以总是会有新的手段来防止我们去绕过 所以我们总是需要学习研究新的东西
0X01 win7内核
为了防止对操作系统的攻击 在win7
下引入了下列防御体系:
使用ASLR
基址随机化使系统运行的进程地址无法被预测
GS
技术保护了返回指针和函数指针 有效的防止了缓冲区溢出的问题
SafeSEH
技术保护了SEH
节点
DEP
技术是堆栈等位置的shellcode
无法执行 这使我们的利用变以困难 而我们都有相对应的方法来绕过这些机制 大致说一下方法
GS突破
这部分大致说的是一些老旧的内容 现在可能部分已经失效 所以我不再大篇幅说很多关于它的东西
未启用GS突破
在内存中并不是所有的函数 所以当某些函数并没有启用GS
功能的时候 我们就可以突破GS
的保护了
虚函数表突破GS
GS
使在函数返回的时候检查Cookie
而没返回之前并没有进行检查 所以我们可以在返回之前劫持程序流 c++
的虚函数就可来实现
异常处理突破GS
覆盖SEH
的函数指针 然后触发异常 使程序转入异常处理 从而执行我们的指针
替换Cookie值突破GS
将栈中的Cookie
和.data
中的Cookie
同时替换我们即可在检查的时候绕过
SafeSEH突破
未启用SafeSEH突破
在某些程序的编译并没有启用SafeSEH
亦或者某个模块并没有启用SafeSEH
的加载模块 这时候我们就可以使用模块中的pop/ret
指令或者其他等价指令来绕过保护
覆盖返回地址突破
当某个函数启用SafeSEH
没启用GS
时 我们将函数返回地址覆盖即可
虚函数突破
类似于GS
DEP突破
未启用DEP突破
依然如上 某个进程未开启DEP
我们攻击此进程即可
执行库代码突破
我们在库中找到执行系统命令的代码 用这段代码的地址覆盖返回地址 从而去执行程序外部的库代码 使其不执行堆栈上的不可执行区域
ret2libc突破
这早在pwn
中就听到过一些 回到我们的老本行了 通过retn
指令在库函数中寻找可用代码攻击
关闭突破
DEP
的设置标志位于内核的KPROCESS
结构中 这个标志可以使用NtQueryInformationProcess
和NtSetInformationProcess
函数通过设置ProcessExecuteFlags
类来查询和修改 使用完NtQueryInformationProcess
再配合ret2libc
的使用 使其再返回到用户控制的缓冲区
可使用ret2libc
的使用VirtualAlloc
和VirtualProtect
函数 使内存可执行 或者通过WriteProcessMemory
将shellcode
拷贝到.code
中执行
1 | kd> dt !_EPROCESS -r |
ASLR突破
覆盖部分返回地址突破
在ASLR
中 我们每次加载的地址部分高位是动态的 相对地址是不变的 修改指针的低位可得到稳定得到 所以我们可以覆盖部分返回地址 使得覆盖后的地址相对于基地址的距离也是固定的
未启用ASLR突破
如上思路
HeapSpray技术突破
使用堆喷射技术 申请大量的内存地址 在其中加上我们的shellcode
WWW的布置
shellcode的布置
首先需要考虑的就是 在内存中如何去布置我们的shellcode
为了保证在内核中执行不出错 我们必须找到一块不怎么使用的地址去存储shellcode
在这篇文章中 可以知道一个函数NtQueryIntervalProfile
而这个函数调用了KeQueryIntervalProfile
函数
1 | kd> u nt!NtQueryIntervalProfile+0x6b //win7 32位 |
1 | kd> u nt!NtQueryIntervalProfile+0x38 //win7 64位 |
进而在该函数中调用了HalDispatchTable
数组
1 | kd> u nt!KeQueryIntervalProfile+0x14 //win7 32位 |
1 | kd> u nt!KeQueryIntervalProfile+0x1d //win7 64位 |
获取ntoskrnl.exe基址
接下来是如何获取HalDispatchTable
地址的问题 首先我们获取到ntoskrnl.exe
的基址 进而根据基址偏移计算出HalDispatchTable
地址
所以 就有了以下代码
1 | #include<stdio.h> |
然后emmm
在WinDbg
中查看 关于名称不一样是操作系统的内核模块根据处理器的个数和是否支持PAE
分为以下四种
ntoskrnl.exe
—Uniprocessor
单处理器,不支持PAE
ntkrnlpa.exe
—Uniprocessor
单处理器,支持PAE
ntkrnlmp.exe
—Multiprocessor
多处理器,不支持PAE
ntkrpamp.exe
—Mulitiprocessor
多处理器,支持PAE
其实是一个东西
获取HalDispatchTable基址
接下来根据偏移 写了如下代码
1 | DWORD64 haldispatchtable() |
然后得到如下
验证一下 get到了
shellcode的编写
使用替换Token
值的方法来编写 所以有了如下的shellcode
1 | //32位下我们使用内联汇编的形式即可 |
1 | //64位的时候可以单独编写.asm文件 |
需要注意的是 我们需要编辑asm
文件的属性参考这里
如图配置 然后应用
在自定义生成工具里面如图配置
shellcode的存放
在做HEVD
系列的时候 我们就做过类似的事情 在32位的时候 我们是这样存放的
1 | WriteWhatWhere->What = (PULONG_PTR)&EopPayload; |
而到了64位 我们得这样存放 分两次
1 | WriteWhatWherelow->What = (PULONG_PTR)&EopPayload; |
0X02 win8内核
SMEP机制绕过
当我们再次使用win7的那套代码去在win8中使用时 就会发现蓝屏
我们去查看一下出错代码0xfc的意思 代表试图执行不可执行的内存 其实在学习pwn的时候 我们就已经接触到了 类似于NX保护的东西 不过在win下叫做SMEP技术
和DEP有点相似 禁止在内核空间执行用户空间的代码
而在ctfwiki上 我们也可以看到和SMEP技术相关的东西
所以我们可以控制CR4寄存器的值来关闭SMEP
而通常使用的方法是 使用ROP技术 在执行ShellCode之前 使用ROP链将CR4寄存器第20位的值置为0
先看一下开启状态下CR4寄存器的值
1 | -->CR4=0x1506f8换为二进制即为1 0101 0000 0110 1111 1000 |
接下来需要寻找一处修改CR4寄存器值的位置 好在师傅们已经做了这部分的工作
1 | uf nt!KiConfigureDynamicProcessor |
而到了win8.1的时候 变为了这样:
然后需要寻找控制rax内容的方法 在ptsecurity的帖子中我们可以看到说到过HvlEndSystemInterrupt提供了控制rax的地方
1 | uf HvlEndSystemInterrupt |
则我们构造的ROP链如下
1 | +------------------+ |
我们将Win7执行ShellCode时的地址换为nt!KiConfigureDynamicProcessor函数对应的相应的地址 然后在这之前将rax的值设置为0x406f8 使其赋值给cr4寄存器 即可关闭SMEP
引入bitmap
而要实现在win8下的任意读写 我们就需要引入bitmap对象了
官方的介绍 是这样
1 | HBITMAP CreateBitmap( |
获取bitmap的地址可以从GdiSharedHandleTable入手 当创建一个bitmap时 一个结构被附加到了父进程PEB的GdiSharedHandleTable
1 | [StructLayout(LayoutKind.Explicit, Size = 256)] |
PEB的该条目是一个指向GDICELL结构体数组的指针 定义了多种image类型
1 | /// 32bit size: 0x10 |
接下来看如何去计算某个进程的bitmap值 先引入一个工具[Process Hacker](Overview - Process Hacker (sourceforge.io)) 该工具可以有效的列出GDI对象句柄
我这里用notepad.exe来举个例子
这里的Handle并不是真正的句柄值 而Object才是我们要的句柄值
但我们可以通过Handle的值去计算Object的值
具体的计算方法如下 即就是使用addr = PEB.GdiSharedHandleTable +(handle&0xffff)* sizeof(GDICELL64)
所以我们也就有了这样的代码去获取bitamp的地址:
1 | #include<Windows.h> |
泄露出来的bitmap内核地址在内核空间指向下面的GDI baseobject结构
1 | /// 32bit size: 0x10 |
在这一头部之后 有一个特定的结构体 类型取决于该对象的类型 我们只关心pvScan0成员即可 该成员指针指向bitmap的首个扫描行
1 | /// 32bit size: 0x34 |
而bitmap配合[GetBitmapBits](GetBitmapBits function (wingdi.h) - Win32 apps | Microsoft Docs)与[SetBitmapBits](SetBitmapBits function (wingdi.h) - Win32 apps | Microsoft Docs)函数即可实现任意读写 GetBitmaps允许我们在pvScan0地址上实现读任意字节 SetBitmapBits允许我们在pvScan0地址上实现写任意字节
则我们的方法为
1 | -->创建两个bitmap对象 一个供读 一个供写 |
写一个粗制的代码便于理解
1 | hManager=CreateBitmap(); |
0x03 win10内核
1511之前的利用
在1511版本之前 我们都可以使用上面所说的方法去进行泄露GdiSharedHandleTable 进而进行进一步操作
1511版本仍然可以正常泄露:
但是到了1607的时候 就发生了变化 同样是可以计算泄露的 但这个指针已经不指向一个有效数据的空间了
1607的利用
在1607版本引入的缓解内核漏洞利用机制是 页表基址在启动时启用随机化处理 使得PTE地址转化算法失效 缓解exp创建ring0下可执行内存的方法
GdiSharedHandleTable表中GDI对象的内核地址被移除了
此时我们已经无法通过这种方法来泄露PrvScan0 所以就需要引入新的方法 所以就有了gSharedInfo结构
1 | typedef struct _SHAREDINFO { |
该结构在User32.dll里被导出 实际上它的值是一个内核结构体win32k!tagsharedinfo 的地址
1 | //在win7 32位下可导出该结构 |
接下来看看如何去计算PrvScan0的地址
1 | -->首先使用LocallAlloc分配一块大小为0x06 * 0x300的内存 |
接下来 贴个代码
1 | int main() |
1703的利用
此时缓解措施体现在 User32.dll模块gSharedInfo结构中的UserHandleTable表发生了变化 原先包含桌面堆中的对象的内核地址信息被移除
在1703版本中 User32.dll模块的gSharedInfo结构中的UserHandleTable表发生了变化 原先包含的桌面堆中的对象的内核地址信息被移除了
1 | //1607版本 |
1 | //1703版本 |
这就意味着我们无法用之前的方法去泄露内核地址了
而在1703版本的方法如下:
1 | -->首先去寻找user32.dll的IsMenu模块 通过该模块去寻找HMValidateHandle函数地址 |
1 | BOOL FindHMValidateHandle() { |
1709的利用
再到了1709版本的时候 我们又无法使用之前的方法了 此时的PvScan0已经放进了堆中 所以需要使用新的方法:
引入Palette
在1709版本 需要引入新的概念 Palette 对象
在32位下 Palette的大小为0x58 64下为0x98
创建Palette 对象 使用CreatePalette函数 该函数只有一个指向LOGPALETTE结构的指针 创建一个Sizeof(PALETTE) + palNumEntries * 4大小的Kernel Pool 来保存Palette结构
1 | typedef struct tagLOGPALETTE { |
palVersion设置为0x300 palNumEntries为palPalEntry的个数 每个palPalEntry结构为4字节的大小
Palette结构:
1 | typedef struct _PALETTE { |
Palette地址泄露
1 | -->创建窗口设置窗口类菜单名称 可以分配任意大小的Kernel Session Pool 来计算Palette结构的地址 |
1 | int main() { |
0x04 小小的总结
目前就只能写到这里了 在17年blackhat上也只介绍到了1703版本 对于缓解措施目前只能从各位师傅的博客 twitter上了解 从而进行总结学习 从一个又一个的防护 再到一个又一个的绕过 师傅们做着各种努力 目前只能跟着慢慢的进行学习 后续学到新的方法机制还会继续写这篇文章 最后 希望对你有所帮助~
0x05 参考链接
wj师傅的指导(由于他本人的博客已经弃了 所以使用google代表他):Google
r00tk1ts的博客:https://r00tk1ts.github.io/
Tj师傅的博客:https://thunderjie.github.io/
sam-b的github:sam-b (Sam Brown) (github.com)
小刀师傅的博客:https://xiaodaozhi.com/
sakura师傅的博客: http://eternalsakura13.com/