Windows Kernel Exploit Part 5
作者:wjllz
来源:先知安全技术社区
前言
Hello, 欢迎来到windows kernel explot
第五篇. 在这一部分我们会讲述从windows 7到windows的各主流版本的利用技巧(GDI 对象滥用). 一共有两篇, 这是上篇.
[+] 从windows 7到windows 10 1607(RS1)的利用[+] windows 10 1703(RS2)和windows 1709(rs3)的利用.
这篇文章的起源来源于我在当时做第三篇博客的时候, 卡在了分析的点, 然后当时开始陷入自我怀疑, 分析和利用究竟哪一个重要一点. 于是, 我把第三篇博客的那个洞就先放了下, 在github上面更新了一个项目. 保证我在windows的主流平台上都能掌握至少一种方法去利用.
学习的过程采用的主要思路是阅读peper
和参考别人的代码实现
, 调试来验证观点. 所以你能看到项目的代码是十分丑陋的, 所以请不要fork… 会有一点点愧疚 :)
项目目前更新到了RS3
, 所以接下来准备的工作是:
[+] 完成RS4和RS5的学习(有相关的的思路, 还未来得及验证)[+] 完成注释和丑陋代码的修缮
希望能够对您有一点小小的帮助 :)
缓解措施的绕过
如果你有阅读过我的系列第二篇的话, 你应该能够知道write-what-where
在内核当中的妙用, rootkits老师
在他的个人博客里面有给出一个相当详细的介绍. 所以在这里我就不再赘述.
我们来回顾一下第二篇的内容:
[+] 利用漏洞构造write-what-where[+] 使用write-what-where替换掉nt!haldispatchTable+8
[+] 替换的时候不直接替换成Shellcode. 替换使程序执行ROP流, 绕过SMEP
[+] 执行shellcode.
==> 找到SYSTEM TOKEN VALUE
==> 找到user process TOKEN addr
==> mov [user process TOKEN addr], VALUE
[+] 提权验证.
可以看到, 我们的思路是在内核当中执行我们的shellcode
, 实现提权. 然后想在内核当中执行shellcode, 就需要绕过各种缓解措施. 那么, 如果我们能够在用户层次实现shellcode
的功能, 是不是就不需要绕过那么多的缓解措施呢. 答案是肯定的(说来这是我刚刚开始做内核就有的一个猜想…)
我们一开始的假设是我们有任意的写能力(write-what-where
), 在项目当中我使用了HEVD模拟. 所以我们解决了:
mov [user process TOKEN addr], SYSTEM TOKEN VALUE
这条指令, 现在的关键点还差下面得两个语句.
[+] 找到`SYSTEM TOKEN` VALUE[+] 找到`USER PROCESS TOKEN`
找到SYSTEM TOKEN VALUE
我们知道我们要找到一个东西的值
, 首先需要找到其地址
. 所以让我们先来找找SYSTEM TOEKN
的地址. 我参考了这里给出的解释, 但是不慌, 让我们先一步一步的来, 先不管三七二十一, 最后再来验证他.
第一步: 找到Ntoskrnl.exe基地址
先来看微软给的一个API函数.
这个函数可以帮我们检索内核当中的驱动的地址. 于是我们依葫芦画瓢. 创建下面的代码.
需要注意的是, 上面的代码的测试环境我是在windows 10 1803
版本做的, 针对windows 1803
以下的版本是同样成立的(当然包括本小节的windows 7- window8.1). 你知道的, 我是一个懒得截图的人 :)
接着做点小小的修改. 会得到下面的结果:
我们可以看到我们找到了ntoskrnl.exe
的基地址. 那么ntoskrnl.exe
是啥呢.
[+] Ntoskrnl.exe is a kernel image file that is a fundamental system component.
我们可以看到Ntoskrnl.exe
包含是一个内核程序, 期间包含一些有趣的信息.
比如 :)
找到PsInitialSystemProcess
我们可以看到PsInitialSystemProcess
存放一个指针, 其指向EPROCESS LIST
的第一个项(也就是我们的SYSTEM EPROCESS). 我们可以利用我们在第二篇当中获取nt!HalDispatchTable的思路来获取它.
代码如下:
调试器的验证结果
现在问题就来了, 我们成功的找到了存放SYSTEM EPROCESS
的地址放在那里, 但是我们却没有办法去读取他(xxx
区域属于内核区域). 我们对内核只有写的权限, 那么我们怎么通过写的权限去获取到读的权限呢. 于是我们的bitmap
闪亮登场 :)
BITMAP 的基本利用
我们来讲一下bitmap的利用思路之前, 先回顾我们在上一篇的内容当中, 已经成功的get到了如何泄露bitmap
地址的能力(你看我安排的多么机智). 所以让我们借助上一篇的代码泄露一个bitmap
观察一一下它的数据.
上面那张图看着有点蒙? 没事的, 让我们先来看看再内核
当中bitmap
对应的结构体.
typedef struct{ULONG64 dhsurf; // 8bytes
ULONG64 hsurf; // 8bytes
ULONG64 dhpdev; // 8bytes
ULONG64 hdev; // 8bytes
SIZEL sizlBitmap; // 8bytes
// cx and cy
ULONG64 cjBits; // 8bytes
ULONG64 pvBits; // 8bytes
ULONG64 pvScan0; // 8bytes
ULONG32 lDelta; // 4bytes
ULONG32 iUniq; // 4bytes
ULONG32 iBitmapFormat; // 4bytes
USHORT iType; // 2bytes
USHORT fjBitmap; // 2bytes
} SURFOBJ64
你可以对照着我给的截图当中的彩色部分, 是我们的关键数据. 蓝色的第一个对应pvBits
第二个对应pvScan0
. 那么, pvScan0
有什么用呢.
pvScan0的作用在bitmap的利用当中尤其重要, 所以我用笨蛋的方法来说明它.
[+] pVscan0指向我们的pixel data. 在CreateBitmap参数当中通过第五个参数传入.[+] pvScan0如果是fffff901`41ffdb88. 那么pixel data(上面的代码样例是"aaaa")就放在fffff901`41ffdb88.
验证
而微软的另外两个API在本例当中也相当重要, 我们来看一下.
他们的作用是.
[+] SetBitMapBits(GetBitmaps)向pvScan0指向的地址写(读)cb byte大小的数据.
所以如果我们假设能够篡改某个(术语 worker bitmap)bitmap
的pvScan0
的值为任意的值的话, 我们就能获取向任意地址读
和写
的权限.
如果你能懂上面那一句话就太好了, 不懂的话让我们通过一个实验来一步一步理解它.
第一步
第一步让我们先来看一份代码, 代码已经更新到我的github上, 你可以在这里找到它同步实验.
看不懂没有关系的, 没有什么比调试器更能帮我们理解代码了. 先来看在运行了这份代码之后, 发生了写什么神奇的事.
第二步.
在这里我们通过上一篇当中的bitmap地址泄露
找到了manager
的pvScan0
的地址.和worker
的pvScan0
的地址. 聪明的你一定能根据前面的结构体明白0x60指的是pvScan0的地址 :)
第三步.
第三步我做了两件事, 第一个单步运行到write_what_where
函数里面. 你可以里面发现什么都没有. 于是我借助于调试器模拟了一次write_what_where
.
[+] 替换manager的pvSca0的内容为worker的pvScan0的地址.
接着我们就可以进行任意读写了. 运行之后就得到了上面的提权. 是不是感觉有点飘. 让我们来分析一下(我比较建议您单步进入WriteOOB函数和ReadOOB观察数据变化, 我比较懒…)
第四步: 任意读
在上面的替换之后(第三步). 我们可以得到我们的manager.pvScan0指向worker.pvScan0. 由上面的截图我们知道.
[+] manager.pvScan0 ==> fffff901`407491e0[+] worker.pvScan0 ==> fffff901`40667cf0
实现了这个之后让我来看看实现任意读呢, 让我们来看看我们的源码:
我们假设我们要将0x4000
的内容读取出来, 那么readOOB
会进行下面的操作.
[+] SetBitmapBits的时候, 向manager.Pvscan0存放的地址(A)出写入0x4000[+] 地址(A)即为worker.pvScan0的地址.
[+] 现在worker.pvScan0存放的地址为0x4000
[+] 调用GitBitmapBits能获取0x4000处的内容
第五步: 任意写
写的操作和读的内容是差不多的, 所以我原封不动的COPY了一份上面的内容, 稍微做了点修改.
我们假设我们要将0x4000
的内容写入值1, 那么readOOB
会进行下面的操作.
[+] SetBitmapBits的时候, 向manager.Pvscan0存放的地址(A)出写入0x4000[+] 地址(A)即为worker.pvScan0的地址.
[+] 现在worker.pvScan0存放的地址为0x4000
[+] 调用SetbitmapBits能对0x4000处的内容进行写操作.
韩国的有位师傅对流程做了一个流程图. very beautiful!!!
第六步: 替换Token
第六步我们发现其实和我们系列一的内容是极其相似的. 只是利用在用户层(我们已经有了任意读写的能力)用c++
实现了汇编的功能. 在这里就不再赘述. 你可以阅读我的系列第一篇获取相关的信息.
总结
在这一篇当中我们讲述了在windows 7. 8 . 8.1 1503 1511
下利用bitmap
实现任意读写的主体思路, 而在接下来的下半部分的文章当中. 我们会讲述在windows 10后期的不同版本当中GDI
的滥用. 也会介绍为什么我们在本篇的方法会什么会在windows 10后期的版本为什么会失效的原因. 敬请期待 :)
相关链接
- sakura师父的博客: http://eternalsakura13.com/
- 小刀师傅的博客: https://xiaodaozhi.com/
- SUFOBJECT64: http://gflow.co.kr/window-kernel-exploit-gdi-bitmap-abuse/
- 我参考的所有资料的PDF: https://redogwu.github.io/
- 我的个人博客: https://redogwu.github.io/
- 我的github上面的那个项目: https://github.com/redogwu/windows_kernel_exploit
- 做实验所需要的代码: https://github.com/redogwu/windows_kernel_exploit/tree/master/windows_8/blog_test_win8.1
以上是 Windows Kernel Exploit Part 5 的全部内容, 来源链接: utcz.com/p/199285.html