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.