当Win32k组件无法正确处理内存中的对象时,Windows即存在一个特权提升漏洞,我对他做了简单的分析.因为感觉作者的poc有一点小问题,所以文末会给出我的魔改版poc.
原作者poc: 0b0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6B7j5h3#2W2M7K6m8^5y4o6m8Q4x3V1k6o6g2V1g2Q4x3X3b7J5x3o6t1H3i4K6u0V1x3o6j5J5y4l9`.`.
Microsoft Windows 10 1903
Microsoft Windows Server 1903
Microsoft Windows 10 1909

tip:因为KALSR的关系,我们分析起来会很麻烦,不过我们可以在调试之前先保存一个快照,这样我们调试的时候就可以先不考虑KALSR.
崩溃之后我们使用!analyze v
来分析一下.首先查看一下错误类型
错误是BAD_POOL_CALLER (c2)
,出现异常的地址为ffffc29800880000
,错误提示是我们释放了错误的内存.没什么头绪,看看堆栈的内容

看起来很像是子函数NewCCI2进行了错误的操作,但其实要稍微复杂一些,后面分析poc源码的时候再说.这里我们只关注win32kfull!Win32FreePoolImpl就好,正是这个函数导致了异常的发生,本来我是想直接给这个函数打断点,但是这个函数会被频繁调用,很不方便.查阅资料发现该函数释放的内存后七位固定为0880000,所以我们可以通过cx寄存器来判断释放的内存是否为一场内存.条件断点如下:
又或者你干脆已经知道了错误内存的地址为ffffc29800880000
,那么直接设置rcx=ffffc29800880000也是ok的.

经历了漫长的条件判断之后我们终于断下来了,现在rcx的值为ffffc29800880000,正是我们的错误内存.检测一下属性

解析不出来,直接查看数据

连Header都没有,这根本就不是Kernel Pool,我们需要继续追踪这块奇怪的内存,从堆栈看一下调用关系

上层函数是Win32FreePool,静态分析一下

Win32FreePool函数仅仅是将参数传递给Win32FreePoolImpl函数而已,再看看上层函数xxxDestroyThreadInfo

不同于以前的win7,在win10上无法查看tagTHREADINFO结构.所以无法得知tagTHREADINFO+0x2c8代表什么,以及是什么函数设置了它的内容,我们尝试继续下断点.
当rsi+0x2C8不为零的时候断下来,查看rsi+0x2c8是否为触发异常的内存.

还是那个熟悉的数字,看来这个地址就是关键,某个函数设置了它的值,并且最终交给xxxDestroyThreadInfo函数来释放他所指向的内存,我们只要一步一步追溯就可以追溯到事发源头.但其实有更方便的法子,我们可以修改一下poc的源码,在一切都发生之前加入一个DebugBreak()断下来,接着对tagTHREADINFO+0x2c8下一个内存访问断点,这样windbg就会自动帮我们找到凶手了.
但是tagTHREADINFO的值每次都会发生变化,所以我们需要再保存一个快照,就在DebugBreak()函数断下来的时候.接着重新找出tagTHREADINFO的值,和刚刚一样:

现在我们恢复到刚刚保存的快照.重新断在DebugBreak()之后,接着我们对ffffc298061d48a0+2c8
下一个内存访问断点并运行

断下来之后我们就可以看到修改ffffc298061d48a0+2c8
的地方,看一下堆栈里面的调用关系

就是win32kfull!xxxSBTrackInit
这个邪恶的函数将错误的地址写入了ffffc298061d48a0+2c8
.我们在ida里面查看一下

上面这个名字长的一批的函数返回了一个指向tagSBTrack结构的指针,之后这个指针将会被写入tagTHREADINFO+0x2c8处,即tagTHREADINFO->pSBTrack.这块内存是由nt!MmCommitSessionMappedView函数分配的,而ExFreePool函数只能释放由ExAllocatePool,ExAllocatePoolWithTag,ExAllocatePoolWithQuota或ExAllocatePoolWithQuotaTag分配的内存,自然会触发异常从而导致BSOD.
因为作者给出了源代码,所以我们接着看一下poc的源代码,我分成几个小部分来一一分析.
OldProtect只是用来保存内存被修改前的访问保护值,teb和peb则分别保存线程环境块和进程环境块.
进程环境块中的KernelCallbackTable保存着函数指针表的副本,KeUserModeCallback通过参数ApiNumber作为索引来选择函数指针表中相应的函数.但是为什么是[2],我们可以在这两句代码之前下一个断点
peb+58的地址就是KernelCallbackTable的地址,这里的[2]是USER32!_fnDWORD.如果我们向滚动条子控件发送WM_LBUTTONDOWN,消息时,会调用到win32kfull!xxxSBTrackInit()
函数,该函数首先会创建一个Session Pool,用来保存 tagSBTrack结构.所以后面我们会特意营造这种情景来调用这个回调函数.
我们在上一步已经得到了指向(peb->KernelCallbackTable)[2]和(peb->KernelCallbackTable)[3]地址的指针,接着我们只要直接赋值就可以hook这两个函数了

OrgCCI2保存原先的函数指针以使用正常的功能,这样我们的hook函数既可以执行我们自定义的操作,还不影响原本的功能.
scrollbar的窗口是可见的, 设置WM_VISIBLE,这样才能成功触发.至此,回调函数也hook完了,窗口也已经创建了,我们可以开始考虑调用我们hook的函数了,具体实现如下
因为被我们hook的两个函数有可能会被其他部分调用,所以我们设置了Flag1和Flag2来跳过我们hook的内容,而去执行OrgCCI2和OrgCCI3,这两个指针保存的正是hook之前的函数指针,这样,其他部分调用hook之后的函数也不会发生异常.
在NewCCI2中,因为Flag已经被置1,所以我们会调用if语句之内的内容,也就是ExitThread(0)
.接着win32kfull!Win32FreePoolImpl
就会调用nt!ExFreePool
来释放tagSBTrack,导致BSOD.
大概流程是这样:
晏子霜师傅本人和博客都有很大帮助,我偷了很多思路和技巧:8cbK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4N6Z5M7$3N6%4L8q4)9J5k6h3&6W2N6q4)9J5c8R3`.`.
wjllz师傅,同样是偷思路和技巧:ba3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6^5P5W2)9J5k6h3q4D9K9i4W2#2L8W2)9J5k6h3y4G2L8g2)9J5c8Y4g2Q4x3V1j5I4x3U0j5H3y4l9`.`.
其他:
badK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2L8Y4q4#2j5h3&6C8k6g2)9J5k6h3y4G2L8g2)9J5c8Y4m8G2M7%4c8Q4x3V1k6A6k6q4)9J5c8U0V1%4y4o6V1^5
5aaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3k6h3c8A6P5g2)9J5k6h3y4G2L8g2)9J5c8X3E0K6M7$3c8Q4x3V1k6H3k6h3c8A6P5e0p5I4i4K6u0r3x3e0l9@1z5e0p5^5i4K6u0W2K9s2c8E0L8l9`.`.
遗憾的是,我没能完成利用.因为我对于类型隔离中分配的这块内存实在是没有办法了,问了一位师傅得到的答复是这是一个利用的可能性微乎其微的漏洞,但微软官方给出的确实是权限提升的通告,所以论坛的师傅们如果有思路的话请分享一下,不胜感激!!!
博客有我的联系方式,欢迎大家来玩,地址:6c0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1H3P5o6u0D9i4K6u0W2j5$3^5`.
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2020-8-7 10:44
被0x2l编辑
,原因: 修改