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:
An overview of all privileges is listed at:
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:
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
But hey… can we not just make these divisions shorter? Yes, we can. I first wanted to show an example query that matches with the math explained above. And yes, indeed, we can shorten this to:
MiscEvents
| where EventTime > ago(24h)
| where ActionType == "ProcessPrimaryTokenModified"
| extend CurrentTokenPrivEnabled = extractjson("$.CurrentTokenPrivEnabled", AdditionalFields, typeof(int))
| where isnotnull(CurrentTokenPrivEnabled) and CurrentTokenPrivEnabled != 0
| extend x = (CurrentTokenPrivEnabled / toint(pow(2, 20))) % 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.