首页
社区
课程
招聘
[原创]CVE-2015-0057提权漏洞学习笔记
发表于: 2022-5-13 23:12 24708

[原创]CVE-2015-0057提权漏洞学习笔记

2022-5-13 23:12
24708

该漏洞是一个UAF漏洞,存在于win32k!xxxEnableWndSBArrows函数中。xxxEnableWndSBArrows函数会对tagWND对象的pSBInfo指针所指向的tagSBINFO结构体中的成员进行计算,但是在计算之前,xxxEnableWndSBArrows函数并没有验证tagSBINFO对象是否存在。如果tagSBINFO对象不存在,此时就会产生写入错误。通过内存布局,可以在释放的tagSBINFO对象中使用tagPROPLIST对象进行填充,那么xxxEnableWndSBArrows函数就会修改tagPROPLIST对象中的成员造成写溢出,利用这个写溢出可以实现任意地址写的操作,最终实现提权。然而,这个实现真的太难了,目前写不出exp利用它,希望以后可以。

操作系统:Win7 x86 sp1 专业版

编译器:Visual Studio 2017

调试器:IDA Pro,OllyDbg,WinDbg

xxxEnableWndSBArrows函数用于开启或关闭一个或两个(水平或垂直)滚动条控件的箭头,窗口对象tagWND中保存了指向保存滚动条对象的结构体指针。tagWND对象的结构体定义如下:

其中偏移0x70保存的是tagSBINFO结构体指针,该结构体保存了滚动条对象的信息,结构体定义如下:

xxxEnableWndSBArrows函数的反汇编代码如下,第二个参数和第三个参数要设定为特定的值来达到漏洞点,暂时先忽略这两个参数。真正产生漏洞的是第一个参数,该参数就是要设置滚动条的窗口对象tagWND。在xxxEnableWndSBArrows函数开始会将tagWND对象的tagSBINFO对象地址取出,之后会调用xxxDrawScrollBar,该函数将会返回用户层,执行用户层的ClientLoadLibrary函数,之后,xxxEnableWndSBArrows函数会对tagSNINFO的第一个成员,即WSBflags成员进行计算。在对WSBflags成员进行计算的时候,xxxEnableWndSBArrows函数并没有验证tagSBINFO对象是否存在,而在调用xxxDrawScrollBar函数的时候,用户可以HOOK ClientLoadLibrary函数,在用户定义的函数中可以释放tagWND对象,导致tagSBINFO对象被释放,从而导致xxxEnableWndSBArrows函数对一个被释放的tagSBINFO对象的WSBflags成员进行计算,造成了错误。

xxxEnableWndSBArrows函数通过xxxDrawScrollBar函数返回到用户层,xxxDrawScrollBar函数通过层层调用返回到用户层的。一步步跟进的话,会发现返回到用户层的调用流程是:xxxDrawScrollBar->xxxDrawSB2->xxxGetColorObjects->xxxDefWindowProc->xxxLoadUserApiHook->xxLoadHmodIndex->ClientLoadLibrary。在ClientLoadLibrary函数中,会调用KeUserModeCallback:

KeUserModeCallback函数实现了win32k的回调机制,调用该函数可以返回到用户层执行用户层的函数,这些用户层的函数被预先设定在了回调函数表中,KeUserModeCallback函数的定义如下:

回调函数表可以认为是一个数组,里面的每一个元素都保存了不同的用户层函数的函数地址。KeUserModeCallback函数的第一个参数就是该回调函数表的索引,通过这个索引就可以从回调函数表中找到要执行的用户层的函数,这里这个索引是0x41。

回调函数表的首地址,保存在了PEB结构体偏移0x2C的KernelCallbackTable成员中

想要查看函数地址表中的内容,只需要随便将一个文件放入到OllyDbg中,在数据窗口首先通过fs:[0x30]获取PEB地址:

在PEB偏移0x2C的地址就可以获取函数地址表的地址:

找到函数地址表的地址,就可以根据索引找到相应的函数,由于函数地址表所指向的是一个4字节的数组,索引索引值乘以4就得到相应的保存了函数地址的地址:

该函数是user32.dll中的函数,根据这里的地址和user32.dll的加载地址就可以算出偏移,最后在IDA中可以看到这个函数是ClientLoadLibrary,只有一个参数且调用声明是__stdcall。

根据上面的过程,可以通过获取PEB中的函数地址表中相应的函数地址来实现HOOK操作,相应代码如下:

要触发该漏洞,首先需要通过CreateWindowEx函数来创建一个带有滚动条控件的窗口对象,CreateWindowEx函数定义如下:

调用该函数时,如果第四个参数dwStyle带有WS_VSCROLL和WS_HSCROLL标记,则创建的窗口就会具有滚动条控件。

在上面的xxxEnableWndSBArrows函数中可以看到,在调用xxxDrawScrollBar返回用户层执行之前,会调用IsVisible对窗口对象tagWND是否可见进行判断。如果不可见,将不会调用xxxDrawScrollBar,因此,创建完窗口之后需要通过ShowWindow函数来让创建的窗口可见,该函数定义如下:

第一个参数即是要操作的窗口对象,第二个参数指定了窗口的属性,当它为SW_SHOW时,该窗口可见,此时IsVisible函数就会返回TRUE。

有了窗口对象,接下来就可以通过EnableScrollBar函数来触发漏洞,该函数最终将会执行win32k中的xxxEnableWndSBArrows。EnableScrollBar函数的定义如下:

该函数的三个参数和xxxEnableWndSBArrows函数的三个参数一样,其中第一个参数就是前面提到的窗口对象。第二个参数wSBflags用来指定要操纵的滚动条,相关定义如下:

第三个参数wArrows是用来表示箭头状态是否是可用的,相关定义如下:

为了利用这个漏洞,此时第二个参数和第三个参数要分别传入SB_BOTH和ESB_DISABLE_BOTH,也就是都传入3,这样可以顺利到达漏洞点。且在最后对对象tagSBINFO的成员WSBflags进行计算的时候,会增大它的值。

综上,最终实现漏洞触发的代码如下:

由上面内容可以知道,漏洞是由EnableScrollBar函数触发的,在触发漏洞之前,可以通过对PEB的函数地址表中相应的函数进行HOOK的方式来实现执行用户需要的代码,所以POC的代码如下:

HOOK以后要执行的函数,无非是将创建的窗口对象释放掉,这样就会相应的释放掉对应的tagSBINFO对象。因为ClientLoadLibrary函数会被多次调用,所以这里使用全局变量g_bFlags和g_dwCount用来实现在漏洞触发的时候会它进行调用。

xxxEnableWndSBArrows函数中下断点,编译运行POC代码。可以看到,在执行xxxDrawScrollBar函数,返回用户层之前,此时的tagWND对象和tagSBINFO对象是存在的:

当执行完xxxDrawScrollBar的时候,tagWND对象就已经被释放,tagSBINFO对象也会跟着释放,原来保存tagSBINFO对象的内存也被修改为特别奇怪的数值,也就意味着此时修改了其他内存的值:

可是继续向下运行,函数依然会对tagSBINFO对象的WSBflags成员进行计算,最终程序继续执行系统虽然没有蓝屏,但是会直接卡死。

xxxEnableWndSBArrows函数会对被释放的tagSBINFO对象进行写入,要利用这个漏洞就需要在tagSBINFO对象的内存区域保存tagPROPLIST对象,该结构体的定义如下:

显然,tagPROPLIST对象是用来保存tagPROP结构体数组的。cEntries用来说明共可以保存多少个tagPROP结构体,iFirstFree保存当前已经保存了多少个tagPROP结构体,aprop则是tagPROP结构体数组。可以通过SetProp函数在tagPROPLIST中增加tagPROP,函数定义如下:

其中参数lpString对应tagPROP结构体中的atomKey,hData对应tagPROP中的hData。当通过SetProp增加tagPROP对象的时候,函数会遍历tagPROPLIST结构体中的tagPROP数组,将其中atomKey和lpString进行对比,如果一样则将hData中的数据替换为调用SetProp函数时指定的第三个参数。如果不一样,就会判断iFirstFree是否小于cEntries,如果不小于则添加失败,否则会重新申请一块内存用来保存tagPROPLIST,因为此时多增加了一个tagPROP原来的内存不够存放了。当tagPROPLIST中包含3个tagPROP对象的时候,此时tagPROPLIST占用的内存就会是0x10 + 0x8 +0x8 = 0x20,而一个tagSBINFO占用0x24字节,此时包含3个tagPROP对象的tagPROPLIST就会占用释放掉的tagSBINFO,xxxEnableWndSBArrows函数在向下继续运行修改tagSBINFO中的WSBflags的时候,就会修改tagPROPLIST中的cEntries(增大它的数值),这样就可以通过SetProp函数来对内存进行写溢出操作。

要实现上面的内容,首先需要通过创建一些窗口,通过调用两次SetProp来设置tagPROPLIST中的数值消耗空余内存。在重复上述调用构造如下的内存布局:

相应的代码如下:

在触发漏洞之前,首先会创建一个带有tagSBINFO对象的窗口对象,这个新建的tagWND和tagSBINFO对象就会占用空闲内存,所以此时的内存布局就会如下:

接下来通过xxxEnableWndSBArrows函数触发漏洞的时候,会首先返回到用户层的ClientLoadLibrary函数,在这个函数中会释放tagWND和tagSBINFO对象,此时在通过SetProp函数增加前面的tagPROPLIST对象中的tagPROP对象,那么增加了tagPROP对象的tagPROPLIST就会占用被释放的tagSBINFO对象,此时的内存布局如下:

所以这个时候,对应的ClientLoadLibrary函数的实现就如下所示:

在xxxEnableWndSBArrows函数下断点,再次编译运行程序,当运行到xxxDrawScrollBar函数返回到用户层之前,此时可以看到tagWND和tagSBINFO是存在的:

执行完xxxDrawScrollBar函数之后,tagSBINFO就会被tagPROPLIST对象占用,且数组中有3个tagPROP元素。

继续向下运行,就会看到函数会将cEntries从0x3修改为0xF:

这个时候继续调用SetProp函数,就会对相邻的内存进行越界写入操作。

想要实现任意地址写入,需要用到tagWND结构体中的strName成员:

该成员是_LARGE_UNICODE_STRING结构体,定义如下:

该成员是用来设置字符串的,可以通过RtlInitLargeUnicodeString函数来初始化要设置的_LARGE_UNICODE_STRING结构体,函数定义如下:

接下来就可以通过NtUserDefSetText函数来将字符串写入到相应的窗口对象中,函数定义如下:

而具体将字符串复制的目标地址,则由_LARGE_UNICODE_STRING中的Buffer成员指定,所以如果可以设置这个成员的值,也就是tagWND对象偏移0x8C处的值为任意地址,就可以通过NtUserDefSetText函数实现任意地址读写。

但是并不能在tagPROPLIST对象后跟tagWND对象,然后通过SetProp时的越界写操作修改tagWND中的Buffer。因为Buffer的偏移是0x8C,不能被8整除,可是用SetProp写入内存的时候,只可以修改前面的6个字节,也就是hData和atomKey。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-5-14 20:10 被1900编辑 ,原因:
上传的附件:
收藏
免费 6
支持
分享
打赏 + 100.00雪花
打赏次数 1 雪花 + 100.00
 
赞赏  Editor   +100.00 2022/06/20 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (4)
雪    币: 200
活跃值: (21)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
粗略看了一遍,感受到了其中的妙处,感谢分享!
2022-5-16 00:51
0
雪    币: 234
活跃值: (816)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
https://blog.diffense.co.kr/2020/03/26/SearchIndexer.html,师傅看看这个漏洞,从漏洞产生上看应该是条件竞争,但我没有复现出来,他的payload是有问题的
2022-7-17 16:03
0
雪    币: 22413
活跃值: (25376)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
4
筱小 https://blog.diffense.co.kr/2020/03/26/SearchIndexer.html,师傅看看这个漏洞,从漏洞产生上看应该是条件竞争,但我没有复现出来,他的payload ...
我目前只调过一些提权漏洞,所以一时半会我也弄不出这个洞的
2022-8-5 10:20
0
雪    币: 234
活跃值: (816)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
1900 我目前只调过一些提权漏洞,所以一时半会我也弄不出这个洞的
这个看文章的描述也是一个提权的洞,不过他的文章中好像有点问题
2022-8-11 09:28
0
游客
登录 | 注册 方可回帖
返回
//