Examining access token privileges with MDATP and Kusto
As a defender, looking at events occurring at user endpoints is very useful. It's essential to know exactly what is happening and insight in detailed log information gives you the opportunity to perform threat hunting and to create detection rules.
It’s a no-brainer that looking at processes on an user endpoint is crucial in order to find adversary’s activities. One of the interesting aspects of the process is the access token. In this blog I will explain briefly what an access token is and how you can use Microsoft Defender ATP (MDATP) and the Kusto query language to examine them in detail.
A SHORT INTRODUCTION TO ACCESS TOKENS AND PRIVILEGES
An often used reconnaissance tool you probably know is “whoami”. It’s a very simple tool telling you who you are (in case you forgot or when you compromised a system and want to know the username of the current user):
C:\WINDOWS\system32>whoami desktop-uaj0h8f\userabc
This tool also has an option called /priv which displays the security privileges of the current user:
C:\WINDOWS\system32>whoami /priv PRIVILEGES INFORMATION ---------------------- Privilege Name Description State ============================= ==================================== ======== SeShutdownPrivilege Shut down the system Disabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeUndockPrivilege Remove computer from docking station Disabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled SeTimeZonePrivilege Change the time zone Disabled
When a user successfully logs on to Windows, the system produces an access token. Every process that’s being created, gets a copy of this token. The access token describes the security context of a process or thread and includes the privileges of the user account.
When the user wants to perform a privileged operation, Windows checks this access token in order to see if the user is allowed to perform that operation. In the example above, the user is allowed to shutdown the system. More information on privileges can be found on:
https://docs.microsoft.com/en-us/windows/win32/secauthz/privileges
An overview of all privileges is listed at:
https://docs.microsoft.com/en-us/windows/win32/secauthz/privilege-constants
The example of privileges above is generated within a normal (unelevated) command prompt. When using an elevated command prompt using an administrator account, you will see a lot more privileges:
C:\WINDOWS\system32>whoami /priv PRIVILEGES INFORMATION ---------------------- Privilege Name Description State ========================================= ================================================================== ======== SeIncreaseQuotaPrivilege Adjust memory quotas for a process Disabled SeSecurityPrivilege Manage auditing and security log Disabled SeTakeOwnershipPrivilege Take ownership of files or other objects Disabled SeLoadDriverPrivilege Load and unload device drivers Disabled SeSystemProfilePrivilege Profile system performance Disabled SeSystemtimePrivilege Change the system time Disabled SeProfileSingleProcessPrivilege Profile single process Disabled SeIncreaseBasePriorityPrivilege Increase scheduling priority Disabled SeCreatePagefilePrivilege Create a pagefile Disabled SeBackupPrivilege Back up files and directories Disabled SeRestorePrivilege Restore files and directories Disabled SeShutdownPrivilege Shut down the system Disabled SeDebugPrivilege Debug programs Disabled SeSystemEnvironmentPrivilege Modify firmware environment values Disabled SeChangeNotifyPrivilege Bypass traverse checking Enabled SeRemoteShutdownPrivilege Force shutdown from a remote system Disabled SeUndockPrivilege Remove computer from docking station Disabled SeManageVolumePrivilege Perform volume maintenance tasks Disabled SeImpersonatePrivilege Impersonate a client after authentication Enabled SeCreateGlobalPrivilege Create global objects Enabled SeIncreaseWorkingSetPrivilege Increase a process working set Disabled SeTimeZonePrivilege Change the time zone Disabled SeCreateSymbolicLinkPrivilege Create symbolic links Disabled SeDelegateSessionUserImpersonatePrivilege Obtain an impersonation token for another user in the same session Disabled
WHY THIS CAN BE INTERESTING FROM A BLUE TEAM PERSPECTIVE
From an offensive perspective, you can imagine the benefits from knowing what privileges a user has. But from a defenders perspective you can also use this to your advantage.
Let’s look at the SeDebugPrivilege. According to the Microsoft documentation this privilege allows the caller all access to the process, including the ability to call TerminateProcess(), CreateRemoteThread(), and other potentially dangerous Win32 APIs on the target process. Examples that need debug privileges are Mimikatz (for accessing the LSASS process to dump credentials) and process injection (to successfully open a handle to a process).
So if we as defenders are able to look for processes that ask for these debug privileges, we are able to potentially see some sneaky behaviour.
MICROSOFT DEFENDER ATP
When looking at the possibilities of Microsoft’s EDR solution MDATP, I found that a lot of interesting events are being logged for example into the MiscEvents table:
One type of events that is being logged is the ActionType==ProcessPrimaryTokenModified. These events contain an AdditionalField column containing JSON data. Zooming in on this JSON data I found two fields: OriginalTokenPrivEnabled and CurrentTokenPrivEnabled. When such an event of ActionType==ProcessPrimaryTokenModified is logged, this means an access token of a process was modified.
The value of this two fields is a decimal, like: 2148532224. No documentation is there yet on those fields. But their names give away a clue:
OriginalTokenPrivEnabled: the original token-privileges that are enabled.
CurrentTokenPrivEnabled: the current (new) token-privileges that are enabled.
My assumption was that these numbers could be a decimal representation of a binary number where each bit represents a specific privilege. I had to look hard to confirm my assumption, because I couldn’t find any Microsoft documentation on it. But the following great presentation confirmed my assumption and explains the access token and privileges in detail:
https://downloads.volatilityfoundation.org/omfw/2012/OMFW2012_Gurkok.pdf
This document describes that an access token in memory is a STRUCT containing an attribute called Privileges. This attribute also contains a STRUCT which holds three 64bit bitmaps:
typedef struct _SEP_TOKEN_PRIVILEGES { UINT64 Present; UINT64 Enabled; UINT64 EnabledByDefault; }
This perfectly confirms the fact that the fields OriginalTokenPrivEnabled and CurrentTokenPrivEnabled are holding a 64bit number containing the privileges. The Volatility document gives an overview of all privileges and their index in the bitmap:
02 SeCreateTokenPrivilege 03 SeAssignPrimaryTokenPrivilege 04 SeLockMemoryPrivilege 05 SeIncreaseQuotaPrivilege 06 SeMachineAccountPrivilege 07 SeTcbPrivilege 08 SeSecurityPrivilege 09 SeTakeOwnershipPrivilege 10 SeLoadDriverPrivilege 11 SeSystemProfilePrivilege 12 SeSystemtimePrivilege 13 SeProfileSingleProcessPrivilege 14 SeIncreaseBasePriorityPrivilege 15 SeCreatePagefilePrivilege 16 SeCreatePermanentPrivilege 17 SeBackupPrivilege 18 SeRestorePrivilege 19 SeShutdownPrivilege 20 SeDebugPrivilege 21 SeAuditPrivilege 22 SeSystemEnvironmentPrivilege 23 SeChangeNotifyPrivilege 24 SeRemoteShutdownPrivilege 25 SeUndockPrivilege 26 SeSyncAgentPrivilege 27 SeEnableDelega2onPrivilege 28 SeManageVolumePrivilege 29 SeImpersonatePrivilege 30 SeCreateGlobalPrivilege 31 SeTrustedCredManAccessPrivilege 32 SeRelabelPrivilege 33 SelncreaseWorkingSetPrivilege 34 SeTimeZonePrivilege 35 SeCreateSymbolicLinkPrivilege
Another way to figure out the index of a privilege is to use the WinDbg command. This also tells you the privileges from a process:
0:001> !token Thread is not impersonating. Using process token… TS Session ID: 0x1 User: S-1-5-21-4073236639-3063625123-4277090652-1001 User Groups: 00 S-1-5-21-4073236639-3063625123-4277090652-513 Attributes - Mandatory Default Enabled ... Privs: 00 0x000000005 SeIncreaseQuotaPrivilege Attributes - 01 0x000000008 SeSecurityPrivilege Attributes - 02 0x000000009 SeTakeOwnershipPrivilege Attributes - 03 0x00000000a SeLoadDriverPrivilege Attributes - 04 0x00000000b SeSystemProfilePrivilege Attributes - 05 0x00000000c SeSystemtimePrivilege Attributes - 06 0x00000000d SeProfileSingleProcessPrivilege Attributes - 07 0x00000000e SeIncreaseBasePriorityPrivilege Attributes - 08 0x00000000f SeCreatePagefilePrivilege Attributes - 09 0x000000011 SeBackupPrivilege Attributes - 10 0x000000012 SeRestorePrivilege Attributes - 11 0x000000013 SeShutdownPrivilege Attributes - 12 0x000000014 SeDebugPrivilege Attributes - 13 0x000000016 SeSystemEnvironmentPrivilege Attributes - 14 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled Default 15 0x000000018 SeRemoteShutdownPrivilege Attributes - 16 0x000000019 SeUndockPrivilege Attributes - 17 0x00000001c SeManageVolumePrivilege Attributes - 18 0x00000001d SeImpersonatePrivilege Attributes - Enabled Default 19 0x00000001e SeCreateGlobalPrivilege Attributes - Enabled Default 20 0x000000021 SeIncreaseWorkingSetPrivilege Attributes - 21 0x000000022 SeTimeZonePrivilege Attributes - 22 0x000000023 SeCreateSymbolicLinkPrivilege Attributes - 23 0x000000024 SeDelegateSessionUserImpersonatePrivilege Attributes -
Where 0x000000014 SeDebugPrivilege is 20 in decimal :)
Back to the ProcessPrimaryTokenModified events in MDATP. We have to convert the decimal number of CurrentTokenPrivEnabled to a binary number and look at bit number 20 in order to see if debug privileges are enabled.
MDATP has the Advanced Hunting functionality where you can use the Kusto (KQL) query language to query against events being logged by MDATP.
Let’s create a Kusto query to get events from MiscEvents that contain access token changes and extract the CurrentTokenPrivEnabled field from JSON and add it to the output as a normal column:
MiscEvents | where EventTime > ago(24h) | where ActionType == "ProcessPrimaryTokenModified" | extend CurrentTokenPrivEnabled = extractjson("$.CurrentTokenPrivEnabled", AdditionalFields, typeof(int)) | where isnotnull(CurrentTokenPrivEnabled) and CurrentTokenPrivEnabled != 0 | project EventTime, ComputerName, InitiatingProcessFileName, CurrentTokenPrivEnabled
CONVERTING DECIMAL TO BINARY WITH KUSTO
So we now have the CurrentTokenPrivEnabled field in our output, but it’s a decimal and we need a binary number. Looking around in the Kusto documentation, I didn’t find any function to do this job. What I did find is a way to use Python within a Kusto query. That’s nice, then converting is as simple as “bin(x)”. However, MDATP doesn’t support Python code within Kusto (it is supported in Sentinal). So this is a bit disappointing, but math comes to the rescue.
A decimal conversion to binary is just math! Starting with the decimal and keep dividing to 2 until you arrive at 0. With every step you divide to 2, you have to calculate the modulo and write down the remainder (which is a 1 or 0). In the end you have set of 1’s and 0’s, which can be put together to have your binary notation of your decimal. Let’s demonstrate this with a simple Python script:
# Manual conversion from decimal to binary d = 2148532224 i = 0 s = '' while d > 0: m = d % 2 d = int(d / 2) s = str(m) + s print('%d\t%d\t%d' % (i, m, d)) i += 1 print("Binary: " + s) 0 0 1074266112 1 0 537133056 2 0 268566528 3 0 134283264 4 0 67141632 5 0 33570816 6 0 16785408 7 0 8392704 8 0 4196352 9 0 2098176 10 0 1049088 11 0 524544 12 0 262272 13 0 131136 14 0 65568 15 0 32784 16 0 16392 17 0 8196 18 0 4098 19 0 2049 20 1 1024 21 0 512 22 0 256 23 0 128 24 0 64 25 0 32 26 0 16 27 0 8 28 0 4 29 0 2 30 0 1 31 1 0 Binary: 10000000000100000000000000000000
Knowing this… we could do such calculation in Kusto to find the 20th bit for debug privileges. I extended my query with 20 divisions and a modulo calculation to find the 20th bit for the debug privileges:
MiscEvents | where EventTime > ago(24h) | where ActionType == "ProcessPrimaryTokenModified" | extend CurrentTokenPrivEnabled = extractjson("$.CurrentTokenPrivEnabled", AdditionalFields, typeof(int)) | where isnotnull(CurrentTokenPrivEnabled) and CurrentTokenPrivEnabled != 0 | extend x = CurrentTokenPrivEnabled/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x/2 | extend x = x%2 | project EventTime, ComputerName, InitiatingProcessFileName , x
The output gives processes where the access token was changed and shows the 20th bit in colum ‘x’. When testing this with processes that asks for debug privileges, I found out that this is indeed the right bit!
For me, as a defender this kind of information is very useful. Imagine what you can do if you combine this with other information that is available.
The origina version fo this blog can be found at https://www.siriussecurity.nl/blog/2019/10/1/examining-access-token-privileges-with-mdatp-and-kusto