Enumeration plays one of the most important roles in red-teaming. The one who is good enough in enumeration, will eventually find a loophole and penetrate through the security layers of the organization. In this post, I will guide you through the code of getting a list of processes running on the target system.
Generally, an executable file is called an Image file because it contains the information of execution. As soon as you click it, the windows process loader will fork the process and load it into memory. The running instance of the image file is now known as the process.
Now you must be thinking that, why do you need this? Why not simply start the tasks of post-exploitation and installing meterpreter persistence, pivoting through networks and stuff? – Well this is OK when you are learning stuff on dedicated attack labs provided by the platforms such as HTB or AttackDefense which gives you a simulated environment. But in the real world, there are a lot of agents and services running in the enterprise systems that would easily detect even the rarest technique you can think of.
Since listing processes is normal as even the employees use it often to list and kill resource-hungry tasks and it will always be off the radar of EDRs and other agents installed on the system, we can leverage this to
- Enumerate the processes to target on the victim's machine
- Identify running antiviruses or agents so that you should always be stealthy
- Get memory dumps of the processes or inject them into the memory to disrupt the logic of the applications
- Other post exploitations like privilege escalation, process migrations, token stealing, impersonation.
Although there are different ways that I will be discussing in the upcoming posts, in this post I will stick to
WTSEnumerateProcessEx API from
Wtsapi32 library in the defined Win32 API. This is because different techniques require different privileges and might return different data.
About WTS Enumerate API
Wtsapi32 is usually used for remote desktop services but can run in a local context by providing the current server handle. Handles are basically the pointers I will discuss in a separate post with you, for now, you can consider them as a reference to a specific object, like the handle to file can be used either to read or write data and server handle is used to query information or execute certain commands on the remote desktop.
We will be using the
WTSEnumerateProcessesExA function defined in the WtsApi32 library.
[in] means the function will accept this argument and
[out] means you pass the variable and it will assign the value to it. Mostly pointer variables will be used in place of
[out] because they store reference which is preserved for the entire scope of the variable or program.
Following is the description of the parameters in the same order as in the function signature
- Since in this case we are concerned with the current desktop, so we can use
WTS_CURRENT_SERVER_HANDLEwhich will tell the function to enumerate the processes from the current server instead
- There are two levels of information you can fetch, and from the infosec perspective, it is recommended to gather as much as information you can. Therefore to get the data of type
WTS_PROCESS_INFO_EX, I have set it to 0x1 here
- When a user logs in to the windows, they create a session. Windows is a multi-user operation and different services run with different users to prevent permission issues and with controlled access extra security. You can get the list of all the processes from all the sessions by sending
WTS_ANY_SESSIONto the function
- This pointer holds the reference to all the information related to processing which is in the
WTS_PROCESS_INFO_EXAstruct. Since the parameter is marked as
[out]this means we are supposed to send the
nullptrand the function will initialize it on its own.
- A reference to the
DWORDvariable that will contain a total number of processes the pointer contains. This will be later used to iterate over the pointer container.
Now since we have understood all about parameters, the function returns
BOOL which we can use to determine whether the execution resulted in success or failure.
Formatting and Printing the Process Details
Since we have a pointer to the struct of
WTS_PROCESS_INFO_EXA which stored
dwProcessCount process entries are in continuous memory, we can use
(pProcesses + c) iterate over each process entry. Skipping the bytes to fit the size of the structure will be taken care of by the compiler. At the low level, it will be translated to
(BASE + sizeof(_STRUCT) * Index)
After printing the basic information the there comes complex information such as getting SID and user details who have started the process (aka effective user in the Linux terms). The SID is the security identification number provided to each user during login and are unique for every user account across the domain. This information is generated for each user by the Local Security Auditor (or
lsass.service) and it is equivalent to a Unix UID system.
It is a PSID structure and you can use the
ConvertSidToStringSidA function from the
sddl.h header which is defined in the
Advapi32.lib library. The function accepts a pointer to the SID object, which you can supply by providing
pUserSid and the second parameter accepts a nullptr initialized PCHAR which will contain the serialized value of the SID. If the function is executed successfully, it will return
FALSE and the possible reason for failures is listed here. You can find this here in the source code
Since this is unique for users across the domain, this means it can be used to get the Domain and account name of the user. Well this can be done by using
LookupAccountSidA function from the
Winbase.h header and defined in
The function parameter description with respect to the source code implemented here are as follows
- Since we want to get the account and domain for the local system only, this parameter must be set to
- Provide the pointer to the SID from
- Provide an array of the
MAX_PATHlength which will hold the account name
- Create a
DWORDand assign the value of
MAX_PATHto it. Then later pass the reference of it to the parameter. On success, this will contain the length of the Name buffer including terminating
- Similar to point 3, create a
CHARarray for the domain name
- Create a
DWORDand assign the value of
MAX_PATHto it. Then later pass the reference of it to the parameter. On success, this will contain the length of the ReferencedDomainName buffer including terminating
- This parameter requires a reference to
SID_NAME_USEenumeration that contains the type of the SID. For example, whether it is a user or a group. The function which returns the serialized type of the token is coded here
Note: The function might fail if the buffer size of
ReferencedDomainName is too small or
cchReferenceDomainName is Zero value
From the unprivileged session, you will see most of the account and domain name lookups will fail because of insufficient permissions and if you try an escalated system, it will still fail for very few of them.
But when you look same processes in the Process Explorer, it will be shown. Well, I have good news for you. I scratched my head for you to find both reason and solution for you.
When you look carefully into the process explorer's own process details, you will see it has SeDebugPrivilege is enabled. You can think of Privileges are like Capabilities in Linux and SeDebugPrivilege is similar to CAP_SYS_PTRACE in Linux. It allows the process to debug its memory, so it will surely be able to get the basic details like the username of the owner.
To know how dangerous SeDebugPrivilege is, I would recommend you to read this post by Microsoft – https://devblogs.microsoft.com/oldnewthing/20080314-00/?p=23113. In the next post, I share a walkthrough of the
AddSeDebugPrivileges() and the
SpawnElevatedProcess() function defined in the