Windows Process Listing Using WTS API – Part 2
In this post, you will learn how to gracefully enable SeDebugPrivilege and automatically launch the process using ShellExecuteExA with administrator privileges. This is in continuation to part 1 of windows process listing using wts api.
This is the second part in continuation to Part 1 of Windows Process Listing Using WTS API. So if you haven't read that, please do it before going forward with this post. In this part, I will be discussing how to enable SeDebugPrivilege in the administrator process and automatically elevate the process if it's running with the least privileged user. So let's first begin with the walkthrough of the AddSeDebugPrivilege()
function in the pch.h
file
Adding Provision for Allowing SeDebugPrivilege
This privilege is already there in the administrator owned processes but not enabled by default because of security reasons. This is being invoked from the main function in the Source.cpp
file in the starting. So let's start its walkthrough as well
First of all, open the process to get its handle. You can get this by calling the OpenProcess()
function from the processthreadapi.h
header file.
The function accepts three parameters, described as follows
- We need to query the information in the process to get and adjust token privileges, so we need to use
PROCESS_QUERY_INFORMATION
from process access rights - Whether or not the process handle is inherited, it doesn't concern us. So let's keep it
FALSE
- Provide the process id for which you want to open the handle. Since we want to open the current process handle, I have used the
GetCurrentProcessId()
function fromprocessthreadapi.h
. Alternatively, you can use theGetCurrentProcess()
function, this will give you the handle withPROCESS_ALL_ACCESS
access right.
NOTE:– Since you will surely get the handle on the current process, but it's a good practice to add safeguard just in case it's blocked somehow. You can do this by checking whether it is NULL
or INVALID_HANDLE_VALUE
. Well in the above function, on failure, it will return NULL
. I won't go deeper in explaining the difference, as it is already explained here
To get the process's token handle, I have used the OpenProcessToken()
function from the processthreadapi.h
header file. If the function succeeds, it will return TRUE.
Provide the handle of the current process handle before this step. The handle must have PROCESS_QUERY_INFORMATION
permission. That is why to have only particular permission in the handle, I have used OpenProcess instead of GetCurrentProcess.
- Not of our concern, simply provide
FALSE
- Create a variable of type HANDLE and pass its reference here to get the token handle
Since we now have all the prerequisites of adding the privilege to the token, let's move to query the privilege value using the LookupPrivilegeValueA()
function from the Winbase.h
header file. If the function executes successfully, it will return TRUE
- Provide
nullptr
here, because we have to perform a lookup on the local system - Since the privilege, we are looking for is "SeDebugPrivilege", pass this string here. You can find all the privilege names listed here
- Create an LUID instance and assign it to
nullptr
and pass its reference here. The function will automatically allocate memory and assign the value to it.
Till now you have completed the first step. The next step is to adjust the token privileges using the AdjustTokenPrivileges()
function from the securitybaseapi.h
header. If the function is executed successfully, it will return TRUE
otherwise FALSE
.
- Provide the token handle with
TOKEN_ADJUST_PRIVILEGES
access right to the handle - We are supposed to enable the privilege, set this parameter to
FALSE
- Create an object of
TOKEN_PRIVILEGE
struct. Set the count of privileges to 1, because we have only one privilege to update. In the second property's first index, assigned the LUID of the SeDebugPrivilege value and attribute set toSE_PRIVILEGE_ENABLED
- The buffer length is required only if PreviousState is passed. Since we are going to query for the privileges again, so this parameter can be
NULL
. - This parameter is not required, so we can set it to
nullptr
- Since the PreviousState is
nullptr
, this parameter can also benullptr
Note:– I also got confused between NULL
and nullptr
. They aren't the same if you are coming from the old school Borland C++ or C background. It's basically introduced to avoid the ambiguity in function overloading with foo(int) and foo(int*). So, if it is a pointer, use nullptr
and it will implicitly be converted to any pointer type.
So at first, the method returned true without enabling the privileges. After looking at the documentation of the function again, I found that AdjustTokenPrivileges()
function cannot add new privileges, but only tries to enable the privileges available in the token. In addition to this, the NewState
parameter can specify privileges that the token does not have, without causing the function to fail. And when the application was executed as a standard user, "SeDebugPrivilege" is not provided to it in the privileges set.
It's better to recheck whether the privileges are enabled or not. If the privilege exists and is enabled then we can move forward otherwise try to start the program with the Administrator user. So to check this, you can use the PrivilegeCheck
function from the securitybaseapi.h
header file. If all the privileges passed in RequiredPrivileges buffer are enabled, this function will return a nonzero response, otherwise FALSE
- Pass in the HANDLE to the token, we have opened it earlier in the previous part of this topic. Make sure that the token must be open for TOKEN_QUERY access
- First of all, create an instance of the
PRIVILEGE_SET
struct. Since we have to look for all the privileges enabled in the PRIVILEGE_SET, set the Control member toPRIVILEGE_SET_ALL_NECESSARY
. Now set the PrivilegeCount to 1, because we have only one privilege for lookup and now in the Privilege array, set the LUID to the value of SeDebugPrivilege we have found earlier.
Now pass the reference of tokPrivSet to the current parameter of the function. On success, this will contain thePRIVILEGE_SET tokPrivSet; tokPrivSet.Control = PRIVILEGE_SET_ALL_NECESSARY; tokPrivSet.PrivilegeCount = 1; tokPrivSet.Privilege[0].Luid = pDebugPriv;
Attributes
field set for the privilege. The attributes values can be found here - Create a
BOOL
variable and pass its reference here. This value will beTRUE
only if all the privileges in the privilege set are enabled. So we can ignore the attributes from the Privilege array and use this value to determine whether "SeDebugPrivilege" is enabled or not
Automatically Elevating Privileges using ShellExecuteExA
Suppose you are running the program in the victim's system. How will you automatically escalate to the Administrator if the unprivileged user is running the system? Well, the answer is asking for the user to "Run as an Administrator".
There are two ways to do that: The first one is using manifest file and set requireAdministator
in the requestedPrivileges
section of the file as shown here and the other is using the ShellExecuteExA
function from theshellapi.h
header file. If the function executes successfully, it will return TRUE
Well in this what we gonna do is "If the program is unable to enable SeDebugPrivilege, it will automatically ask for the elevation via UAC and start an elevated process add SeDebugPrivilege and continue further dumping all the process details". Isn't this sound interesting 😄. There is only one parameter, so lets breakdown it's parameters.
- After you have instantiated the object of the
SHELLEXECUTEINFOA
struct, compute the size of the struct and assign it to cbSize - Use the default masks for the application as this doesn't concern us for now. You can set fMask to
SEE_MASK_DEFAULT
- We are supposed to create an independent process with no message boxes, so set hwnd property to
nullptr
to prevent inheriting from the parent window. - This is now the main thing! To request for administrator elevation, we should set lpVerb to runas
- To provide which file to run, we have to first find the path of the currently running process and assign it to lpFile. Well, it can be done by calling the
GetModuleFileNameA()
function from thelibloaderapi.h
header file, implemented at the starting of the SpawnElevatedProcess function in thepch.h
file - We don't have any additional parameters, so you can set lpParameters to
nullptr
- After the elevation, it will open a command prompt and print all the details on the standard output, it is required to show the normal window. We need to pass
SW_NORMAL
(for more values) value to nShow.
Pass the function reference of the SHELLEXECUTEINFOA
object to ShellExecuteExA function and close the current running program using exit(1)
function.
You can see the working of the code in the following GIF! It looks pretty awesome 😊
Resources
- https://docs.microsoft.com/en-us/windows/win32/shell/launch
- https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
- https://stackoverflow.com/questions/20509734/null-vs-nullptr-why-was-it-replaced
- https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-adjusttokenprivileges
- https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupprivilegevaluea
- https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesstoken
- https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443
- https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-privilege_set
- https://stackoverflow.com/questions/15967949/how-to-run-application-with-admin-privileges-using-manifest-file-in-visual-studi
- https://docs.microsoft.com/en-gb/windows/win32/api/shellapi/nf-shellapi-shellexecuteexa?redirectedfrom=MSDN
- https://www.delftstack.com/howto/cpp/exit-in-cpp/
- https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea