Straight Outta Script Kiddie Zone : Deep dive on how to get a SYSTEM shell on Windows

Feb 1 · 7 min read

Gather around, neighbors! It is time for a short pentest adventure story.
In a recent engagement, we found ourselves having issues to get a command prompt under nt authority\system context, even if we had an administrator access to our target (what a shame😅).
Instead of drowning in our failure and accept our lack of skills, I decided to throw away my script kiddie side and take a few hours to think about the situation.

The first question that crossed my mind then was : Why we are failing at this ? Well the answer was clear : We did not know at the time what we were doing, we were running tools like PSExec, WMIExec left and right without really understanding how things works. And to be honest when I think about calling PSExec in order to get an nt authority\system shell, I realize how lack of understanding lead us to use a Swiss Army knife as a screwdriver (that being said using Psexec will give you a system shell … in most cases 😶).

Local Administrator Account VS NT AUTHORITY\SYSTEM

As you may know, having local administrator privileges does not mean you can do what ever you want on the system. So for example using Mimikatz’s lsadump::sam or call LsaRegisterLogonProcess()at this stage was not possible for us unless we manage to have nt authority\system context. And this all made sense when I started learning about Access Tokens. After all we could have run token::elevate before lsadump::sam to get it all done, but as I said we had no idea on what we are doing and how the tools are working 👎 👎 But let’s go back to the Access Tokens thing.

According to the Windows documentation, An access token is an object that describes the security context of a process or thread. The information in a token includes the identity and privileges of the user account associated with the process or thread. When a user logs on, the system verifies the user’s password by comparing it with information stored in a security database. If the password is authenticated, the system produces an access token. Every process executed on behalf of this user has a copy of this access token.

The security context we have using the Local Administrator account with UAC consent looks like this :

And in the other hand the access token under nt authority\system is more like this :

As we can see there is some Privileges that are specific to the nt authority\system account. Which means : in order to get an access similar to ROOT in Linux we need to have a SYSTEM context. In reality this is what is happening behind the scenes when we are calling token::elevate.

Why the hell can’t we do it ourselves ?

At this stage I was quiet curious on how can we have a shell as nt authority\system , and I did not want to rely on another tool again. So I decided to dive a little deeper in the beautiful world of Windows Access Tokens .

By reading the Windows documentation I learned that every process has a primary token that describes the security context of the user account associated with the process. But more interesting the Windows API offers a whole set of Functions to manipulate Access Tokens, such as enable/disable the privileges in an access token, create a new primary token that duplicates an existing one, and so on … With all of these functions, it is possible for a thread to impersonate a client account and get a copy of it’s primary token, we call this process Impersonation.

Impersonation is the ability of a thread to execute using different security information than the process that owns the thread. So basically we can use the Windows API Calls to get a copy of a process primary token and use it to start a new program that has the same exact context. And of course in this case the end goal is to target an existing process running as SYSTEM and use the copy of its token to start a new shell which will hopefully be running with nt authority\system context 🙏

Nothing new so far … but let’s do this !

Of course nothing of this is new to the Offsec community but now that I had a clear idea on what to do, and how many OSTs (Long live the OSTs ❤) are abusing Impersonation, I felt confident enough to write my own implementation of the process and use it to get our nt authority\system command prompt😃. And to make the journey more fun I decided to use C# as I never wrote anything in this language, and I thought it would be such a great introduction.
The first step in this process was to identify the PID of a process running as nt authority\system . This can be done with the following Powershell command:

Enabling SeDebugPrivilege with AdjustTokenPrivileges function

In order to obtain a handle to the primary token of the nt authority\system process we are targeting, we need to have a specific Privilege which allows use to debug programs SeDebugPrivilege . And if you remember the Access Token we got using a Local Administrator (see image above ☝️), the SeDebugPrivilege was available. But it was marked as Disabled. As we will be using this specific privilege in the process, it is mandatory to enable it before proceeding. This is where the function AdjustTokenPrivileges can be useful. Since this is a part of the unmanaged code of a Windows API we need to declare and import all the signatures/structures we need to successfully call AdjustTokenPrivileges . So the following C++ signature given in the windows documentation :

Can be declared in C# like this : ( All credits to for the amazing Wiki )

Now that we have a function that can enable a specific privilege we can use it to enable the SeDebugPrivilege for the current process. We just need to use a few other API calls : GetCurrentProcess to get a handle for the current process, OpenProcessToken to get a handle for the Access Token of the current process, LookupPrivilegeValue to retrieve a unique identifier of the privilege we are looking for and finally the AdjustTokenPrivilege call to change the state of that privilege. After a few more definitions :

We can now use the following code to enable SeDebugPrivilege ✔️:

myAPI is just a class where I put all the PInvoke signatures and definitions

Get a handle for the Primary Token With OpenProcess and OpenProcessToken functions

Armed with SeDebugPrivilege we can now access the primary token for a system process and get a copy of it. For this we will use OpenProcess to get a handle to the targeted process ( the PID is obtained using the command earlier ☝️). Then we call OpenProcessToken one more time to get a handle to its token. According to the windows documentation the OpenProcess C++ signature is like :

Which translates in C# to :

And the code block that allow us to get a handle to the primary token which we will be storing at tokenHandle variable :

Duplicate the token and spawn a new process using DuplicateTokenEx and CreateProcessWithTokenW functions

This the part when the magics finally happens, with a handle to the token we have so far, we can duplicate it and use the duplicated token to create a new process which will use the duplicated token as a primary token. The new process we will be creating is nothing else than cmd.exe . As we will use 2 new API calls we still have some more definitions and structure to declare :

At this stage we should have all the prerequisites to achieve our goal. The following *ugly* C# code does the trick :

And by the end of the day, we build and run the project feeding it with the PID of a non protected SYSTEM process like Winlogon :

That’s it, we finally got our meterpreter’s getsystem alternative. This concludes our little adventure. As I said we did nothing revolutionary but it feels great to understand how things work.



Written by


Fighting impostor syndrome | I Pentest in production

More From Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade