该漏洞存在于win32k中的xxxNextWindow函数中,该函数没有对tagWND对象的spmenu成员的合法性进行验证,就直接将其作为合法地址进行读写,导致了BSOD的产生。通过将spmenu设置为特定的值,可以让tagWND对象获得越界写入的能力,从而修改其他tagWND的strName->Buffer的值,实现任意地址写入,最终实现提权。
操作系统:Win 7 x86 sp1 专业版
编译器:Visual Studio 2017
调试器:IDA Pro, WinDbg
xxxNextWindow函数的第一个参数指向tagQ结构体,该结构体定义如下:
tagQ结构体中有许多成员指向tagWND结构体,该结构体定义如下:
xxxNextWindow会从tagQ中取出偏移0x28的spwndActive作为参数调用GetNextQueueWindow,spwndActive代表当前活跃的窗口,函数GetNextQueueWindow会将spwndActive偏移0x38保存的子窗口spwndChild返回:
验证GetNextQueueWindow返回的tagWND偏移0x78处保存的spmenu成员是否为0,如果不为0,将对spmenu偏移0x14处指向的地址进行或运算:
spmenu是一个tagMENU对象,该对象部分成员如下,所以上面的代码其实是想对tagMENU的flags成员进行或运算。
在进行或运算之前,函数只是验证了tagWND->spmenu是否为0,却没有验证tagWND->spmenu所指的内存是否有效,而spmenu可以通过函数SetWindowLong修改,该函数定义如下:
SetWindowLong函数会调用内核的xxxSetWindowLong函数实现功能,在xxxSetWindowLong函数中会判断nIndex是否小于0:
如果nIndex小于0,则会调用xxxSetWindowData函数:
xxxSetWindowData函数会判断nIndex是否等于-12:
如果nIndex为-12,继续判断tagWND->style的最高位是否为0x40,如果是,则将dwNewLong赋值给窗口的spmenu:
根据以下定义可以知道,当对带有WS_CHILD标记的窗口调用SetWindowLong的时候,如果第二个参数传入GWL_ID,第三个参数的值就会写入到窗口的spmenu中:
因此,用户可以修改窗口的spmenu,如果将它修改为一个非法的地址,再调用xxxNextWindow函数,就会因为对一个非法地址进行或运算导致BSOD的产生。
对上述内容的总结如下:
漏洞函数xxxNextWindow的执行流程是:1) 获取当前活跃窗口;2) 从当前活跃窗口中获取子窗口;3) 对子窗口的spmenu成员0x14所指的内存地址进行或运算
想要在用户层修改窗口的spmenu成员,要满足的条件:1) 被修改的窗口具有WS_CHILD标记. 2) 调用SetWindowLong的时候第二个参数指定为GWL_ID
xxxNextWindow函数需要通过发送Alt + Esc的按键信息来调用,因此漏洞触发的步骤如下:
获取桌面窗口句柄作为父窗口
创建一个用来触发漏洞的攻击窗口,该窗口的父窗口需要指定为桌面窗口
设置攻击窗口带有WS_CHILD标记,通过SetWindowLong修改攻击窗口的spmenu,将其指向非法内存地址
将桌面窗口置于最顶层,即将其变成活跃窗口
模拟Alt + Esc按键调用xxxNextWindow函数
相应的POC代码如下:
在xxxNextWindow函数验证spmenu是否为0处下断点,编译运行POC,可以看到此时的spmenu已经被修改为指定的值:
由于是非0的,所以函数会继续向下执行,将spmenu的值取出,并对其+0x14的内存地址进行或操作,但是该地址是非法地址,所以会触发BSOD错误:
利用该漏洞可以实现对指定的内存地址与4进行或运算,因此可以将spmenu赋值为tagWND偏移0x93,这样就可以扩大tagWND偏移0x90的cbwndExtra。之所以扩大这个地址,是因为在xxxSetWindowLong中,当nIndex大于0的时候,函数会判断nIndex + 4是否小于等于cbwndExtra,如果大于,就会设置错误码再退出函数:
如果nIndex + 4小于等于cbwndExtra,函数会对将dwNewLong的值写入到tagWND窗口之后的nIndex处的地址。这里的esi指向tagWND的首地址,0xB0是tagWND的大小,edx则是nIndex:
如果利用该漏洞将cbwndExtra扩大了,此时就可以直接通过SetWindowLong来实现越界写入操作。如果可以在可写的范围内在布置一个tagWND结构体,这样就可以通过SetWindowLong函数修改新布置的tagWND结构体中的成员来实现任意地址读写。
由于要获取tagWND的cbwndExtra地址来对其进行扩大,所以要首先获取窗口对象的tagWND地址。tagWND的获取可以通过HMValidateHandle函数实现,该函数定义如下:
当第二个参数type指定为TYPE_WINDOW(0x1)的时候,函数会返回第一个参数指定的窗口句柄的THREADESKEAD结构体,该结构体定义如下,其中pSelf成员保存了tagWND对象在内核中的地址。
有了HMValidateHandle函数就可以获取需要的窗口的tagWND在内核中的地址,但是该函数是未导出的函数,需要通过user32.dll中的导出函数IsMenu来获取,因为IsMenu函数有对HMValidateHandle的调用:
这里可以将0xE8作为特征码找到HMValidateHandle的相对地址,根据相对地址得出实际的函数地址,相应的代码如下:
通过HMValidateHandle可以获取cbwndExtra,在调用SetWindowLong修改spmenu的时候,可以将写入值指定为cbwndExtra + 3 - 0x14的地址,这样就可以利用或操作扩大cbwndExtra,相应代码如下:
此时在xxxNextWindow对spmenu是否为0处下断点,查看此时的tagWND地址为0xFEA1C200,那么cbwndExtra的地址就是0xFEA1C290:
spmenu为0xFEA1C27F,等于0xFEA1C200 + 0x90 + 0x3 - 0x14,成功指向cbwndExtra + 0x3处的地址,此时的cbwndExtra为0。
继续向下运行,会对tagWND中成员cbwndExtra + 0x3地址处的内容对4进行或运算:
可以看到此时成功将cbwndExtra值扩大到0x04000000:
现在可以利用漏洞实现扩大cbwndExtra成员的方式来实现越界写入,需要在被扩大的tagWND对象的可写入的范围内布置另一个tagWND结构体作为用来修改的窗口对象,通过对该对象成员的修改实现任意地址读写,所以此时需要首先创建一部分窗口,在释放掉其中的一部分,这样触发漏洞的时候创建的窗口之后就会存在另一个tagWND对象,相应代码如下:
此时,在触发漏洞的时候,加入以下代码用来查找位于被扩大cbwndExtra的tagWND对象高地址中最近的一个tagWND对象用来作为攻击窗口此时,就可以利用越界写入修改攻击窗口对象tagWND中的成员。
任意地址的读取要用到GetAncestor函数,该函数的定义如下:
该函数会调用内核中的NtUserGetAncestor,NtUserGetAncestor函数将_GetAncestor函数返回值指向的地址中的值作为返回值:
参数gaFlags为1的时候,_GetAncestor函数会返回tagWND中偏移0x34的spwndParent:
所以,在用户层调用GetAncestor函数的时候,如果第二个参数指定为GA_PANT,函数就会将第一个参数对应的tagWND对象的spwndParant所指的内存地址中的数据返回。
因为可以利用越界写入操作将tagWND偏移0x34的spwndParent修改为任意地址,所以,可以通过调用GetAncestor就可以实现任意地址的读取。
对于任意地址写入,需要用到tagWND对象偏移0x84处的strName成员:
该成员用来是一个_LARGE_UNICODE_STRING结构体,结构体定义如下:
在用户层调用如下定义的SetWindowTextW函数的时候,函数会将参数lpString中的数据写入到tagWND->strName的Buffer保存的地址中。同理,因为此时可以利用越界写入操作将Buffer修改为任意地址,所以,可以通过SetWindowTextW实现任意地址写入。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-6-29 15:27
被1900编辑
,原因: