首页
社区
课程
招聘
[原创]CVE-2018-8453提权漏洞学习笔记
发表于: 2022-7-28 11:10 18538

[原创]CVE-2018-8453提权漏洞学习笔记

2022-7-28 11:10
18538

由于win32kfull中的NtUserSetWindowFNID在对窗口对象的fnid进行设置的时候,没有判断该窗口是否已经释放,这样就可以对一个已经释放的窗口进行fnid的设置。而在xxxSBTrackInit和xxxFreeWindow中都存在用户层的回调,通过对函数的劫持可以在回调中释放掉xxxSBTrackInit函数中使用的tagSBTRACK结构体,这样当xxxSBTrackInit释放该结构体的时候就会因为双重释放导致BSOD的产生。通过xxxSBTrackInit函数释放结构体之前会对结构体中的部分成员进行解引用的操作,在相应的内存地址中放置PALETTE的cEntries成员的地址来利用解引用扩大该值,实现越界地址写入相邻PALETTE的pFirstColor来实现任意地址读写,最终实现提权。

操作系统:Win10 x64 1709 专业版

编译器:Visual Studio 2017

调试器:IDA Pro, WinDbg

由于在Win10系统中,很多符号并没有导出,所以一些结构体成员只能通过分析得到。对于窗口对象tagWND,和本次漏洞有关的成员定义如下:

tagWND偏移0x52的fnid表明了窗口的状态,当值包含0x8000的时候,表示窗口被释放:

tagWND偏移0x10的pti指向tagTHREADINFO结构体,改结构体保存了线程信息,定义如下:

tagTHREADINFO偏移0x198的pq指向tagQ结构体,结构体定义如下:

tagTHREADINFO偏移0x2B0指向tagSBTRACK结构体,当鼠标在一个滚动条按下左键的时候,系统会通过该结构体用来标记鼠标的当前状态,结构体定义如下:

内核通过xxxFreeWindow来释放窗口,函数会将要释放的窗口对象的fnid与0x8000进行或运算,表示窗口被释放。接着判断窗口对象是否有扩展内存,即cbwndExtra是否为0,如果不为0,则执行xxxClientFreeWindowClassExtraBytes来释放扩展内存:

xxxClientFreeWindowClassExtraBytes函数会执行KeUserModeCallback来返回用户层:

xxxClientFreeWindowClassExtraBytes函数执行完成之后,函数会判断窗口对象的fnid值的低12位是否在0x2A0到0x2AA之间:

如果fnid在0x2A0到0x2AA之间,则会调用SfnDWORD函数:

SfnDWORD函数也会调用KeUserModeCallback函数返回用户层:

xxxSBTrackInit是用来执行鼠标左键按下滚动条进行拖动的函数,该函数的部分代码如下,函数首先申请一块内存用来保存pSBTrack结构体,对这块内存进行初始化,并对部分成员进行引用;接着函数调用xxxSBTrackLoop用来处理拖动滚动条要处理的消息;最后函数对相关成员进行解引用后,释放掉pSBTrack结构体:

xxxSBTrackLoop会调用xxxTranslateMessage和xxxDispatchMessage来分发处理消息,xxxDispatchMessage函数会调用上面说的SfnWORD函数来返回用户层:

该函数用来设置窗口对象的fnid增加指定的值,但是,这里增加的时候,函数没有判断窗口是否已经被释放,即是否具备0x8000。这就会导致,进行设置的时候很有可能会对一个已经释放的窗口的fnid值进行设置:

这个漏洞的成因比较复杂,要将上面的几个函数都联系起来看,成因如下:

当向滚动条控件发送WM_LBUTTONDOWN(左键按下)的消息时候,xxxSBTrackInit函数就会被调用,xxxSBTrackInit函数会调用xxxDispatchMessage,该函数又会调用SfdDWORD来返回用户层

如果用户HOOK了用户层对应的处理函数,就可以在该函数中调用DestroyWindow来释放拥有该滚动条控件的窗口。这样就会执行xxxFreeWindow,该函数会首先将窗口的fnid标记为删除的窗口,接着在该窗口存在扩展对象的时候,调用xxxClientFreeWindowClassExtraBytes函数返回用户层

如果用户HOOK了用户层对应的处理函数,就可以在处理函数中调用NtUserSetWindowFNID,将窗口的fnid加入0x2A1的标记。这样xxxClientFreeWindowClassExtraBytes函数返回以后,会因为被修改的窗口的fnid值的低12位为0x2A1导致再次调用SfdDWORD返回用户层,此时在对应的处理函数中释放掉xxxSBTrackInit函数申请的pSBTrack结构体,这块内存就会处于释放状态

当xxxFreeWindow函数返回后,就会返回到xxxSBTrackInit继续执行,而xxxSBTrackInit会在最后释放pSBTrack结构体,而这个结构体已经被释放,此时如果在释放就会产生BSOD错误

要成功触发这个漏洞,就需要在SfdDWORD在用户层的处理函数中释放pSBTrack结构体,此时只需要通过向滚动条发送WM_CANCELMODE消息,该函数会导致xxxEndScroll函数来释放内存,该函数的主要代码如下:

其中第二处的限制需要窗口一个新得滚动条对象,并对其调用SetCapture。整个触发漏洞的流程如下:

创建一个带有八字节额外内存的窗口对象,并将该对象的句柄赋值到额外内存中供之后使用。同时,在该窗口中在创建一个滚动条对象用来触发漏洞

HOOK SfdDWORD和xxxCreateFreeWindowClassExtraBytes返回到用户层会执行的函数

向创建的窗口发送WM_LBUTTIONDWORD函数来调用xxxSBTrackInit函数

在用户层定义的xxxClientFreeWindowClassExtraBytes会根据要释放的内存中保存的是否是第一步中窗口的窗口句柄,来判断是否要修改fnid和调用SetCapture

在用户层定义的SfdDWORD的处理函数中,会判断如果是第一次调用就会通过DestroyWindow来调用xxxFreeWindow。如果是第二处调用,则发送WM_CANCELMODE来是否pSBTrackInit函数

当xxxSBTrackInit函数最后释放pSBTrack结构体的时候就会因为双重释放导致BSOD

相应的POC代码如下:

编译运行POC就会产生BSOD错误,以下是部分错误信息:

可以看到BSOD产生的原因就是因为重复释放pSBTrack结构体:

想要不产生BSOD,就需要在第一次释放pSBTrack结构体之后,申请一块共占用0x80大小的内存来占用释放掉的内存,这样在xxxSBTrackInit中第二次释放的时候不会因为释放掉已释放的漏洞产生双重释放。而在xxxSBTrackInit释放内存之前,函数会对其中几个成员通过调用HMAssignmentUnlock来解引用,该函数的实现如下,可以看出就是将传入参数的指针所指向地址偏移为8的地址的值-1,如果可以传入合适的参数来扩大特定的值就可以实现任意地址读写。

之前往往选择BitMap对象,但因为从Win10 1709开始,BitMap对象的data不在对象头之后,pvScan0指向了不同的内存,所以这里就改为选用Palette对象来实现任意地址读写。

创建Palette对象的创建通过CreatePalette函数来实现,该函数的定义如下:

其中参数的定义如下,成员palNumEntries指定了数组palPalEntry的个数:

数组palPalEntry的类型为PALETTENTRY,可以看出每个元素占用4字节:

CreatePalette调用成功之后,就会创建一个PALETTE对象,该对象定义如下,其中偏移0x1C的cEntries等于LOGPALETTE的palNumEntries,偏移0x88的apalColor数组即LOGPALETTE中的palPalEntry数组。

而偏移0x78的pFirstColor指向了apalColors,当通过SetPaletteEntries和GetPaletteEntries进行读写的时候,读写的地址就是由pFirstColor指向的地址:

当创建带有MenuName的窗口时,可以通过tagWND偏移0xA8的pcls来获取其在内存中的地址。因此,可以通过创建一个这样的窗口,然后释放掉,在马上创建一个PALETTE,那么它的pcls就指向了这个PALETTE对象(大概率),获取MenuName地址的代码如下,这里需要注意的是创建的内存如果打到一定程度,这块内存的头就不会有0x10的POOL_HEADER,所以使用的时候要根据具体情况来决定。

此时可以通过创建一个0x1000字节的MenuName,在释放掉它并马上申请两个占用0x800的PALETTE,这样这两个PALETTE就会占用释放掉的MenuName,它们在内存中是相邻的,且可以获取到第一个PALETTE对象的地址,此时记录第一个PALETTE对象cEntries对象的地址供之后使用,相应的代码如下:

接下来在第一次释放的时候,就要创建大量的MenuName来占用释放的内存,由于可以直接修改MenuName的值,所以可以对应tagSBTRACK结构体的成员,将其设为上面记录的cEntries成员的地址,这样之后xxxSBTrackInit函数在最后调用HMAssignmentUnlock进行-1操作的时候就会修改cEntries,相应的代码如下:

在触发漏洞之前可以看到,创建的两个0x800大小的PALETTE对象的第一个PALETTE对象的cEntries为原来的大小:

触发漏洞,进行两次-1操作以后,就被扩大成一个很大的值:

此时就可以通过函数越界读写相邻的那个PALETTE对象的pFirstColor指针来实现任意地址读写了,相应代码如下:

可以进行任意地址读写,就可以通过修改Token来实现提权:

由于触发漏洞的时候,xxxSBTrackInit会释放掉MenuName所指的内存,为了防止在进程退出,释放资源的时候再次对其进行释放导致双重释放,就需要通过前面在g_ulMenuName中保存的地址修改为NULL来解决该问题,对应代码如下:

完整的代码保存在:https://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2018-8453.cpp。运行程序就可以成功提权,且退出程序的时候不会产生BSOD错误:


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-8-11 20:53 被1900编辑 ,原因:
收藏
免费 5
支持
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/08/12 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (1)
雪    币: 864
活跃值: (5124)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
每日一阅,提神醒脑
2022-8-8 17:46
0
游客
登录 | 注册 方可回帖
返回
//