-
-
[原创]CVE-2015-2546提权漏洞学习笔记
-
发表于: 2022-3-16 19:49 8618
-
和CVE-2014-4113一样,这个漏洞也是因为调用xxxSendMessage函数的时候,没有对第一个参数进行合法性验证。用户可以通过一定的方法修改第一个参数的值,导致可以通过xxxSendMessageTimeout中的以下代码实现提权:
与CVE-2014-4113不同的是,这次的漏洞在xxxHandleMenuMessage中调用的xxxMNMouseMove函数中。
操作系统:Win7 X86 sp1 专业版
编译器:Visual Studio 2017
调试器:IDA Pro,WinDbg
要理解这个漏洞,需要对两个结构体有所了解,第一个是tagMENUWND,该结构体定义如下:
其中最关键得是最后一个成员,也就是偏移0x0B0处得PopupMenu,该成员是一个tagPOPUPMENU结构体,结构体定义如下:
在xxxMNMouseMove函数中,首先会调用xxxMNFindWindowFromPoint函数获取返回值,而该函数的返回值是通过xxxSendMessage发送0x1EB的消息得到的。因此,对0x1EB的消息进行挂钩就可以修改此处的返回值,这里的返回值需要修改成tagMenuWnd(原因在下面)。为了触发漏洞,此处的返回值就不能是-1或-5,这样才能绕过2,3的验证,而由于4的验证,返回值还需要是一个合法的窗口的值,因为在IsWindowBeingDestroyed函数中,会对窗口的属性进行检查。
接下来,函数会将tagMenuWnd中偏移0xB0处保存的结构为tagPOPUPMENU的pTagMenuWnd取出,将它作为第一个参数调用xxxMNHideNextHierachy。可是在此之前,函数通过xxxSendMessage发送了两次消息,而且这两个函数的参数都是pTagMenuWnd。那么,用户就可以在通过挂钩的方式,在调用xxxMNHideNextHierachy函数之前对tagMenuWnd中的数据进行更改,也就是更改偏移0xB0处的pTagPopupMenu。
这里IDA反编译的结果出了问题,两个xxxSendMessage函数发送的消息应当分别是0x1E5和0x1F0,因为它们的汇编代码分别如下:
在xxxMNHideNextHierarchy函数中,会将pTagPopupMenu偏移0x0C的spwndNextPopup作为第一个参数调用xxxSendMessage函数。
虽然这里第八第九行代码验证了spwndNextPopup是否为0,但是,函数没有对pTagPopupmenu进行验证。因此,只要修改掉spwndNextPopup,让它不为0。
最终将导致代码xxSendMessageTimeout中的call dword ptr [esi + 0x60]就变成call dword ptr [spwndNextPopup + 0x60],此时只要在相应的位置放入ShellCode函数地址,就可以实现提权。
和CVE-2014-4113相同的是,该漏洞也需要通过TrackPopupMenu函数来触发,所以也需要以下的代码来创建相应的窗口
不同的地方在于此时触发漏洞的位置是不同的,以下是部分代码:
经过计算,uMsg等于0x201的时候,会触发CVE-2014-4113的漏洞,当它等于0x200的时候会触发CVE-2015-2546的漏洞。而uMsg的值是由主窗口处理例程中的第三个PostMessageA的第二个参数决定的,根据以下的定义可以知道,本次漏洞的触发需要通过PostMessageA发送WM_MOUSEMOVE的消息
并且,在进入xxxMouseMove函数中,会对第三个PostMessageA的第四个参数进行判断,该值如果为0,将不会调用xxxMNHideNextHierarchy触发漏洞,所以此次的exp的主窗口例程的处理函数应该改成如下的代码:
根据上面的漏洞分析可以知道,要成功调用xxxMNHideNextHierachy函数触发漏洞,需要对0x1E5,0x1EB和0x1F0这三个消息进行处理。对于0x1E5,只需要返回0x10就符合要求,而0x1EB需要返回一个合法的窗口对象来绕过IsWindowBeingDestory函数。因此,需要通过CreateWindowsExA创建一个类名为 "#32768"的窗口对象,函数会返回一个合法的tagMENUWND的窗口对象,当处理0x1EB消息的时候,将该对象返回回去就可以绕过验证。
有了tagMENUWND对象,接下来就需要在0x1F0中想办法修改tagMENUWND对象的tagPOPMENU中偏移0xC的spwndNextPopup。因为,在函数xxxMNHideNextHierarchy中,会对这个成员判断其值是否为0,如果不为0,才会将其作为第一个参数传给xxxSendMessage函数,而上面所创建的类名为"#32768"的tagMENUWND对象的tagPOPMENU中的spwndNextPopup为0。
想要将其修改为非0值,此处需要使用到CreateAcceleratorTable来创建加速表对象,该函数的定义如下:
其中第二个参数指定了要创建加速表对象的个数,这里需要指定为0x5,因此每个加速表对象占8字节,5 * 8 = 0x28,在加上8字节的POOL_HEADER以及0x10字节的对象头,这样可以保证创建的空间足够容纳0x40大小的tagPOPUPMENU结构体。因此,消息0x1EB的执行内容如下:
先销毁掉"#32768"窗口对象,这样会释放掉tagPOPUPMENU的空间
申请一些的加速表对象,这些加速表对象就会占用刚刚释放"32768"窗口对象的tagPOPUPMENU空间,这样,就可以修改tagPOPUPMENU中的spwndNextPopup
为了避免内存块合并,在初始化阶段,需要首先耗尽0x40大小的内存块。然后申请一批连续的0x40大小的加速表对象,释放掉其中的偶数部分的加速表对象,这样创建的"32768"窗口对象的tagPOPUPMENU的空间就会在在这些释放的内存块中。而它的前后两块都是正在被使用的内存块,当在消息0x1EB中释放tagPOPUPMENU的时候,不会发生内存块的合并,。因此,最终的初始化函数应当修改为如下:
菜单窗口的处理例程,则应该如下所示:
参考下面链接的师傅的代码写的exp,链接在:https://github.com/LegendSaber/exp/blob/master/exp/CVE-2015-2546.cpp。
在xxxMNMouseMove函数中下断点,运行exp,当运行到发送0x1F0的消息时,此时可以看到#32768窗口对象的tagPOPUPMENU中偏移0xC的spwndNextPopup为0
同时,查看这个时候的内存情况,tagPOPMENU对象在两块被使用的加速表对象中间,此时释放它不会造成内存块的合并
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- [原创]CVE-2022-21882提权漏洞学习笔记 16382
- [原创]CVE-2021-1732提权漏洞学习笔记 19489
- [原创]CVE-2014-1767提权漏洞学习笔记 15192
- [原创]CVE-2018-8453提权漏洞学习笔记 18526
- [原创]CVE-2020-1054提权漏洞学习笔记 13542