在今年年初谷歌的安全人员公布了一个由于DCE属性导致的uaf,其公布的poc如下:
从谷歌安全研究员给出调试信息我们可以了解到这是一个tagwnd对象导致的uaf。
在分析漏洞之前我们先了解一下本次漏洞相关的两个属性:CS_CLASSDC和CS_OWNDC,当windows对象被设置了CS_CLASSDC属性并且所属窗口类没有被设置dce对象时,在调用CreateWindow创建窗口的过程中,内核会调用CreateCacheDc,创建DCE对象(下图节选自wrk):
同样通过上图可以看出被设置了CS_OWNDC属性时,内核也会创建DCE对象,不同的在于设置CS_CLASSDC属性时,创建的DCE是被赋值给窗口所属的窗口类对象:
而若是设置的CS_OWNDC属性,创建的DCE对象会被直接赋值给窗口对象:
也就是说一个dce对象的所有权有两种情况:一是属于某一个窗口对象,另一中情况就是属于某一类窗口,即挂靠在tagcls对象中。我们再看看一直在关注的dce对象结构(节选自wrk):
结构体中的pwndorg便是本次uaf的对象
接下来了解一下在关闭释放窗口对象时,对于dce对象内核是如何处理的(以下截图源自该漏洞修补之前的win32kfull.sys):
第一个if的意思是,当pdce是属于窗口类对象的,那么便调用InvalidateDCE,清空dce中的pwnd相关字段,第二个if的意思是,如果pdce是属于该窗口的,那么不仅要清空dce中的内容,还要释放该dce,咋看似乎逻辑上并没有什么问题,但对照poc我们便能发现问题所在:
在调用CreateWindowEx创建WindowA时,由于并未设置CS_OWNDC或者是CS_CLASSDC属性,内核并不会创建dce对象,接下来调用SetClassLong函数修改WindowA的类属性,添加上CS_CLASSDC属性,那么在调用CreateWindowEx创建WindowB时,内核会创建一个dce对象(我们称其为dce_a),其所有权归窗口类pcls所有,并且此时dce对象中的pwndOrg对应于WindowB的窗口对象,接下来调用GetDc(WindowA),由于其窗口类具有CLASSDC,那此时GetDc便会返回dce_a对应的句柄hdc_a,并且此时dce_a中的pwndorg变为windowa对应的窗口对象,紧接着调用SetClassLong函数添加了CS_OWNDC属性,那么这时候再调用CreateWindowEx创建WindowC,由于窗口同时具有CLASSDC属性 和CS_OWNDC属性,内核不仅会新创建一个属于窗口WindowC的dce对象(dce_b),同时窗口类拥有的dce对象也会从dce_a,变为dce_b.总结一下此时内核中对象的状态,dce_a对象中的pwndorg对应于windowA,窗口类pcls拥有的对象为dce_b,那么当我们释放windowA时,再看看上面提到的逻辑(此时pdce对应与dce_a,pwndorg对应于windowA):
无论是上面的if还是下面的if,两个条件都不满足,也就是说当windowA被释放时,dce_a中的pwndorg并不会清0,仍然保存着一个被释放的窗口对象,
最后我们调试poc,来验证我们的分析推测,在poc中加入断点以方便调试:
设置如下断点:
bp win32kbase+0x39b0a “r @eax;r @$t1=poi(@ebp+0x8);r @$t1”
bpCVE_2018_0744!main+0xf7———-代表在GetDC(WindowA);处中断
其中eax代表新创建的dce对象,t1代表与之关联的窗口对象,
运行到第一个int3,并没有发生任何中断,g继续运行,输出如下:
查看windowb的值:
对上号了,也就是创建完窗口windowb后,新建了一个pce对象901fa198,它存储的pwndorg为窗口b–95e148a0:
在执行完GetDC(WindowA)后内存发生如下变化:
也就是说pdce对象的pwndorg变为窗口a–95e14220,我们看看窗口a所属的窗口类对象pcls中包括pdce对象是什么:
这会儿是901fa198
一直运行到最后一个int 3:
创建了一个新的dce对象–901b01b8 ,我们继续看看窗口a所属的窗口类对象pcls中包括pdce对象是什么:
可以看到已经变成新生成的901b01b8,也就是这时能绕过xxxfreewindow中的两个if判断。
最后我们看看在窗口A释放后,dce对象901fa198中的pwndorg是否认为窗口A:
Gu等释放完成后看看pdce901fa198中的pwndorg:
可以看到依旧是窗口a–95e14220
这样pdce中便保存了一个被释放的窗口对象,但再次引用时便出现了uaf。
最后看看微软的修补方案:
补丁前:
补丁后:
也就是把poc中绕过两个if条件的情况也包括进去清空dce中的窗口对象了,这样也就避免uaf了。
从这个案例可以总结出在win32k模块中uaf出现的场景不少是设计架构时的逻辑错误导致的,作为安全研究人员,咱们更多的从宏观思考或许能有更大的收获。