Catch logo at GISEC 2024

Manipulating Windows Tokens with Go

Written by Swapnil
Co-founder @ FourCore
Manipulating Windows Tokens with Golang

At FourCore, we are building a security control validation platform, FourCore ATTACK. We have developed real-world cyber attack simulations, accurately mimicking an attacker's behaviour. This is enabled by our agents for Windows and Linux developed in Go. These are lightweight agents which connect to our SaaS platform to deliver attack simulations.

This article discusses the challenges we faced while developing the Windows agent around accessing, manipulating, and utilizing the different types of Windows Tokens via Go.

We have also open-sourced wintoken, A Go package to steal Windows tokens, enable/disable token privileges and grab interactive and linked tokens.

Privileged Service or Unprivileged User?

When a threat actor compromises a machine, the permissions he gets significantly impact how successful the attack would be. For our specific use-case at FourCore, running as a single user chosen during install time will limit us, and we will have a very tunneled view of security gaps. Therefore, it is essential to run attack simulations as different users, to understand how the impact of an attacker can vary between users.

The FourCore ATTACK Windows agent is available as a pre-configured MSI to the customer and installed as a service on the customer's endpoint to be targeted for attack simulations via the platform. We run as a Windows service which allows performing actions as different users depending on the simulation's requirements.

Users are tricky; they can be local admins or standard users and be logged in or signed out. We cannot and do not ask for or store user account passwords and don't know when they might turn their system off. We cannot depend on the user for our simulations.

Go supports Windows API with the x/sys/windows sub-repository but not all Windows APIs are directly available. Microsoft is slowly changing this with their win32metadata project, but there still is a long way to go. As we will discuss in the next few sections, we use various Win32 APIs via Go to perform attack simulations as different users on Windows depending on the simulation's requirements.

Let's take a little first to introduce the concept of Windows access tokens and how they dictate what we can or cannot do on the system.

What are Windows Tokens

Tokens on Windows are immutable structures stored in the Kernel, containing identity and privilege information. An all-powerful system token is created when the computer starts, but for user activities, you get an access token after you log in. Also, whenever any local admin logs in, there are two tokens, one for unelevated uses, aka filtered admin token and the other for elevated tasks, aka admin token. These tokens are bound to one another and are known as Linked Tokens.

Windows Logon process for a user
Windows Logon process for a user

There are two types of access tokens, Primary Token and Impersonation Token. Primary Tokens are called process tokens, as primary tokens can only be attached to processes. Impersonation tokens are called thread tokens, as impersonation tokens can only be attached to threads. Although these tokens can be freely converted to each other using the DuplicateTokenEx API.

Any program started by the user has the access token associated with it and is known as the Primary Access Token. By default, child processes inherit their Parent's token and privileges. When you log in with an appropriate password, winlogon.exe, with some magic, starts explorer.exe with a proper token, and you finally land on your desktop with the privileges provided to you by your organisation.

Interactive Token for Windows Service

Even though our agent runs as a Windows service, and we have system-level access, running with the correct user privileges becomes a difficult task. For example, do we steal the elevated or filtered token if the interactive user is a local admin? Should we even steal the token or create a new one using an API such as LogonUser? Would token stealing be considered a malicious activity?

Before you decide to steal a token from explorer.exe, let me tell you that as a Service on Windows, you have access to this great set of APIs available to fetch the interactive token of the user currently logged into the system.

You can enumerate (WTSEnumerateSessionsA) to fetch the active session (WTSGetActiveConsoleSessionId) on the system, query the token (WTSQueryUserToken) associated with that session, and finally duplicate that token so that you can start a new process (CreateProcessAsUserA) as that interactive user. If the user is a local admin and you want to run in an elevated manner, you can also get the linked token (TOKEN_LINKED_TOKEN) using the Windows API.

Get Interactive token as a service
Get Interactive token as a service

You will be amused to know that some APIs have a bit of weird behaviour, such as OpenProcessToken's API docs mention that you need PROCESS_QUERY_INFORMATION in desired access, but it also works with PROCESS_QUERY_LIMITED_INFORMATION.

Elevated Token: Privileges and Integrity Levels

Each token has some associated privileges, which dictate what actions the user of the token can perform. Since a token is an immutable kernel object, you cannot add or remove privileges, only enable or disable them using the AdjustTokenPrivileges API. All the privileges associated with a token also have a minimum integrity level needed to enable those privileges. For example, without running with High Integrity, you cannot enable SeDebugPrivilege. This is an additional security measure taken by UAC designers to prevent access to securable objects.

Tokens have integrity levels associated with them. Integrity Levels are a way of controlling access to securable objects and are evaluated before other access policies (DACLs). For example, a process running with an integrity level set to Low cannot interact with objects that have a medium integrity level. You can think of the integrity Level as a broad classification of securable objects. Some groups of objects(placed in high integrity) need to be more secure than other groups of objects(placed in medium integrity). Windows defines four integrity levels: Low, Medium, High, and System. A standard user has a medium integrity level, and elevated users have a high integrity level. Windows takes steps that an object with low integrity cannot access objects with high integrity.

There are some Privileges known as Super Privileges, which, if present in a token, would mean that the token is an elevated token. Some examples of these Super Privileges are SeCreateTokenPrivilege, SeTcbPrivilege, SeTakeOwnershipPrivilege, SeLoadDriverPrivilege, SeDebugPrivilege, SeBackupPrivilege, etc.

Let's join all of these together for a quick recap in a little step-by-step.

  1. You log in with your Username and Password.
  2. Winlogon creates a primary token that contains the privileges assigned to you.
  3. You cannot add or remove privileges as the token is stored in Kernel and is immutable.
  4. If you logged in as a Local Admin, you get one elevated token and one non-elevated token.
  5. An elevated token has some super privileges, and you can query this token using GetTokenInformation API.
  6. Since an elevated token has super privileges, it is of High Integrity, whereas a non-elevated token, by default, is of Medium Integrity.
  7. You cannot add or remove any privileges from the token as it is an immutable object.

wintoken: Go package to manipulate Windows tokens

Manipulating Windows APIs to simulate advanced real-world threats is core to our platform. Stealing tokens, impersonating users, enabling privileges, etc is core to how our Windows agent and attack simulations work. We built the wintoken package for our own use-cases to simplify working with tokens.

The package should help you abstract away many pain points we faced with Windows Tokens during development. The package exposes functions to steal tokens, enable/disable privileges, and grab interactive and linked tokens. It's licensed under the permissive MIT License and you can freely use it in your own projects.

Here are a few examples on how to use wintoken:

  • To steal a token from a process, you can use OpenProcessToken and supply the PID and the type of token that you want
1package main
2
3import (
4	"os/exec"
5	"syscall"
6
7	"github.com/fourcorelabs/wintoken"
8)
9
10func main() {
11	token, err := wintoken.OpenProcessToken(1234, wintoken.TokenPrimary) //pass 0 for own process
12	if err != nil {
13		panic(err)
14	}
15	defer token.Close()
16
17	//Now you can use the token anywhere you would like
18	cmd := exec.Command("/path/to/binary")
19	cmd.SysProcAttr = &syscall.SysProcAttr{Token: syscall.Token(token.Token())}
20}
  • If you want the elevated interactive token for the currently logged in user, you can call GetInteractiveToken with TokenLinked as parameter
1package main
2
3import (
4	"os/exec"
5	"syscall"
6
7	"github.com/fourcorelabs/wintoken"
8)
9
10func main() {
11	//You can get an interactive token(if you are running as a service)
12	//and specify that you want the linked token(elevated) in the same line
13	token, err := wintoken.GetInteractiveToken(wintoken.TokenLinked)
14	if err != nil {
15		panic(err)
16	}
17	defer token.Close()
18
19	//Now you can use the token anywhere you would like
20	cmd := exec.Command("/path/to/binary")
21	cmd.SysProcAttr = &syscall.SysProcAttr{Token: syscall.Token(token.Token())}
22}
  • Once you have a token, you can query information from this token such as its privileges, integrity levels, associated user details, etc.
1package main
2
3import (
4	"fmt"
5
6	"github.com/fourcorelabs/wintoken"
7)
8
9func main() {
10	token, err := wintoken.OpenProcessToken(1234, wintoken.TokenPrimary)
11	if err != nil {
12		panic(err)
13	}
14	defer token.Close()
15
16	fmt.Println(token.GetPrivileges())
17	fmt.Println(token.GetIntegrityLevel())
18	fmt.Println(token.UserDetails())
19}
  • You can Enable, Disable, and Remove privileges in a simple manner
1package main
2
3import(
4	"github.com/fourcorelabs/wintoken"
5)
6
7func main(){
8	token, err := wintoken.OpenProcessToken(1234, wintoken.TokenPrimary)
9	if err != nil {
10		panic(err)
11	}
12	//Enable, Disable, or Remove privileges in one line
13	token.EnableAllPrivileges()
14	token.DisableTokenPrivileges([]string{"SeShutdownPrivilege", "SeTimeZonePrivilege"})
15	token.RemoveTokenPrivilege("SeUndockPrivilege")
16}

Conclusion

Understanding Windows Tokens is essential whether you are from a Red Team or Blue Team or just developing software that would run on windows. It is not a small topic, and this blog introduced little concepts and a library to simplify some token manipulation tasks. I have linked references from which you can gain a deeper understanding of Windows tokens and their critical nature.

References