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
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_INFORMATIONfrom process access rights
- Whether or not the process handle is inherited, it doesn't concern us. So let's keep it
- 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
processthreadapi.h. Alternatively, you can use the
GetCurrentProcess()function, this will give you the handle with
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
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
- 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
nullptrhere, 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
nullptrand 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
- Provide the token handle with
TOKEN_ADJUST_PRIVILEGESaccess right to the handle
- We are supposed to enable the privilege, set this parameter to
- Create an object of
TOKEN_PRIVILEGEstruct. 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 to
- 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
- This parameter is not required, so we can set it to
- Since the PreviousState is
nullptr, this parameter can also be
Note:– I also got confused between
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
- 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_SETstruct. Since we have to look for all the privileges enabled in the PRIVILEGE_SET, set the Control member to
PRIVILEGE_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 the
PRIVILEGE_SET tokPrivSet; tokPrivSet.Control = PRIVILEGE_SET_ALL_NECESSARY; tokPrivSet.PrivilegeCount = 1; tokPrivSet.Privilege.Luid = pDebugPriv;
Attributesfield set for the privilege. The attributes values can be found here
- Create a
BOOLvariable and pass its reference here. This value will be
TRUEonly 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 the
shellapi.h header file. If the function executes successfully, it will return
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
SHELLEXECUTEINFOAstruct, 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
- We are supposed to create an independent process with no message boxes, so set hwnd property to
nullptrto 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 the
libloaderapi.hheader file, implemented at the starting of the SpawnElevatedProcess function in the
- We don't have any additional parameters, so you can set lpParameters to
- 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
You can see the working of the code in the following GIF! It looks pretty awesome 😊