Skip to Main Content
September 05, 2023

Creative Process Enumeration

Written by Oddvar Moe
Red Team Adversarial Attack Simulation

Very often in engagements, you'll want to list out processes running on a host. One thing that is beneficial is to know is if the processes is a 64-bit or 32-bit process. Why do you need to know the process architecture, you might ask? The reasons are many, but one common example is that you might find a process running as system that is vulnerable to an escalation attack (DLL hijack or similar technique). Then, you would need to craft your payload according to the architecture. You would think that you could use scripts and code to list out the process architecture pretty easily. At least I thought so when I started looking into this. Let’s take a quick look at a GUI example using process explorer to list out the architecture.

As you can see from the unelevated Process Explorer, you can only see the Image Type for process launched by the user. This is due to the fact that a non-elevated user cannot get details about system processes or processes launched by other users. It states <access denied> on the username field amongst other processes. Process Explorer cannot show it, but the native Task Manager in Windows can. All you have to do is to add the column named Platform. Using the Task Manager however you can see that you are not allowed to see the UAC Virtualization for any other users process.

If we try to list out a process using PowerShell’s get-process, you will see this output:

(Get-Process)[10] | fl *

Name                       : explorer
Id                         : 4364
PriorityClass              : Normal
FileVersion                : 10.0.19041.2311 (WinBuild.160101.0800)
HandleCount                : 2523
WorkingSet                 : 156053504
PagedMemorySize            : 59518976
PrivateMemorySize          : 59518976
VirtualMemorySize          : 613797888
TotalProcessorTime         : 00:00:16.1875000
SI                         : 2
Handles                    : 2523
VM                         : 2203932020736
WS                         : 156053504
PM                         : 59518976
NPM                        : 111184
Path                       : C:\Windows\Explorer.EXE
Company                    : Microsoft Corporation
CPU                        : 16.1875
ProductVersion             : 10.0.19041.2311
Description                : Windows Explorer
Product                    : Microsoft® Windows® Operating System
__NounName                 : Process
BasePriority               : 8
ExitCode                   :
HasExited                  : False
ExitTime                   :
Handle                     : 2768
SafeHandle                 : Microsoft.Win32.SafeHandles.SafeProcessHandle
MachineName                : .
MainWindowHandle           : 131348
MainWindowTitle            :
MainModule                 : System.Diagnostics.ProcessModule (Explorer.EXE)
MaxWorkingSet              : 1413120
MinWorkingSet              : 204800
Modules                    : {System.Diagnostics.ProcessModule (Explorer.EXE), System.Diagnostics.ProcessModule (ntdll.dll), System.Diagnostics.ProcessModule (KERNEL32.DLL),
                             System.Diagnostics.ProcessModule (KERNELBASE.dll)...}
NonpagedSystemMemorySize   : 111184
NonpagedSystemMemorySize64 : 111184
PagedMemorySize64          : 59518976
PagedSystemMemorySize      : 1099280
PagedSystemMemorySize64    : 1099280
PeakPagedMemorySize        : 71675904
PeakPagedMemorySize64      : 71675904
PeakWorkingSet             : 211505152
PeakWorkingSet64           : 211505152
PeakVirtualMemorySize      : 820936704
PeakVirtualMemorySize64    : 2204139159552
PriorityBoostEnabled       : True
PrivateMemorySize64        : 59518976
PrivilegedProcessorTime    : 00:00:09.2968750
ProcessName                : explorer
ProcessorAffinity          : 1
Responding                 : True
SessionId                  : 2
StartInfo                  : System.Diagnostics.ProcessStartInfo
StartTime                  : 8/25/2023 4:48:07 AM
SynchronizingObject        :
Threads                    : {4368, 4628, 4684, 4692...}
UserProcessorTime          : 00:00:06.8906250
VirtualMemorySize64        : 2203932020736
EnableRaisingEvents        : False
StandardInput              :
StandardOutput             :
StandardError              :
WorkingSet64               : 156053504
Site                       :
Container                  :

As you can see, there's nothing here that immediately sticks out. Namely, whether the process is a 64-bit process or not. Let’s also do the same using vbscript together with WMI to list out the processes. For this demonstration, I am going to use WMI Explorer to generate a sample vbscript code that includes all process properties. The script looks like this:

On Error Resume Next

Const wbemFlagReturnImmediately = &h10
Const wbemFlagForwardOnly = &h20

Set wshNetwork = WScript.CreateObject("WScript.Network")
strComputer = wshNetwork.ComputerName

strQuery = "SELECT * FROM Win32_Process"

WScript.StdOut.WriteLine ""
WScript.StdOut.WriteLine "====================================="
WScript.StdOut.WriteLine "COMPUTER : " & strComputer
WScript.StdOut.WriteLine "CLASS    : ROOT\CIMV2:Win32_Process"
WScript.StdOut.WriteLine "QUERY    : " & strQuery
WScript.StdOut.WriteLine "====================================="
WScript.StdOut.WriteLine ""

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\ROOT\CIMV2")
Set colItems = objWMIService.ExecQuery(strQuery, "WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)

For Each objItem in colItems

    WScript.StdOut.WriteLine "Caption: " & objItem.Caption
    WScript.StdOut.WriteLine "CommandLine: " & objItem.CommandLine
    WScript.StdOut.WriteLine "CreationClassName: " & objItem.CreationClassName
    WScript.StdOut.WriteLine "CreationDate: " & objItem.CreationDate
    WScript.StdOut.WriteLine "CSCreationClassName: " & objItem.CSCreationClassName
    WScript.StdOut.WriteLine "CSName: " & objItem.CSName
    WScript.StdOut.WriteLine "Description: " & objItem.Description
    WScript.StdOut.WriteLine "ExecutablePath: " & objItem.ExecutablePath
    WScript.StdOut.WriteLine "ExecutionState: " & objItem.ExecutionState
    WScript.StdOut.WriteLine "Handle: " & objItem.Handle
    WScript.StdOut.WriteLine "HandleCount: " & objItem.HandleCount
    WScript.StdOut.WriteLine "InstallDate: " & objItem.InstallDate
    WScript.StdOut.WriteLine "KernelModeTime: " & objItem.KernelModeTime
    WScript.StdOut.WriteLine "MaximumWorkingSetSize: " & objItem.MaximumWorkingSetSize
    WScript.StdOut.WriteLine "MinimumWorkingSetSize: " & objItem.MinimumWorkingSetSize
    WScript.StdOut.WriteLine "Name: " & objItem.Name
    WScript.StdOut.WriteLine "OSCreationClassName: " & objItem.OSCreationClassName
    WScript.StdOut.WriteLine "OSName: " & objItem.OSName
    WScript.StdOut.WriteLine "OtherOperationCount: " & objItem.OtherOperationCount
    WScript.StdOut.WriteLine "OtherTransferCount: " & objItem.OtherTransferCount
    WScript.StdOut.WriteLine "PageFaults: " & objItem.PageFaults
    WScript.StdOut.WriteLine "PageFileUsage: " & objItem.PageFileUsage
    WScript.StdOut.WriteLine "ParentProcessId: " & objItem.ParentProcessId
    WScript.StdOut.WriteLine "PeakPageFileUsage: " & objItem.PeakPageFileUsage
    WScript.StdOut.WriteLine "PeakVirtualSize: " & objItem.PeakVirtualSize
    WScript.StdOut.WriteLine "PeakWorkingSetSize: " & objItem.PeakWorkingSetSize
    WScript.StdOut.WriteLine "Priority: " & objItem.Priority
    WScript.StdOut.WriteLine "PrivatePageCount: " & objItem.PrivatePageCount
    WScript.StdOut.WriteLine "ProcessId: " & objItem.ProcessId
    WScript.StdOut.WriteLine "QuotaNonPagedPoolUsage: " & objItem.QuotaNonPagedPoolUsage
    WScript.StdOut.WriteLine "QuotaPagedPoolUsage: " & objItem.QuotaPagedPoolUsage
    WScript.StdOut.WriteLine "QuotaPeakNonPagedPoolUsage: " & objItem.QuotaPeakNonPagedPoolUsage
    WScript.StdOut.WriteLine "QuotaPeakPagedPoolUsage: " & objItem.QuotaPeakPagedPoolUsage
    WScript.StdOut.WriteLine "ReadOperationCount: " & objItem.ReadOperationCount
    WScript.StdOut.WriteLine "ReadTransferCount: " & objItem.ReadTransferCount
    WScript.StdOut.WriteLine "SessionId: " & objItem.SessionId
    WScript.StdOut.WriteLine "Status: " & objItem.Status
    WScript.StdOut.WriteLine "TerminationDate: " & objItem.TerminationDate
    WScript.StdOut.WriteLine "ThreadCount: " & objItem.ThreadCount
    WScript.StdOut.WriteLine "UserModeTime: " & objItem.UserModeTime
    WScript.StdOut.WriteLine "VirtualSize: " & objItem.VirtualSize
    WScript.StdOut.WriteLine "WindowsVersion: " & objItem.WindowsVersion
    WScript.StdOut.WriteLine "WorkingSetSize: " & objItem.WorkingSetSize
    WScript.StdOut.WriteLine "WriteOperationCount: " & objItem.WriteOperationCount
    WScript.StdOut.WriteLine "WriteTransferCount: " & objItem.WriteTransferCount
    WScript.StdOut.WriteLine ""


For example, the output from the svchost.exe process looks like this:

Caption: svchost.exe
CreationClassName: Win32_Process
CreationDate: 20230825044553.785112-420
CSCreationClassName: Win32_ComputerSystem
Description: svchost.exe
Handle: 3044
HandleCount: 204
KernelModeTime: 312500
Name: svchost.exe
OSCreationClassName: Win32_OperatingSystem
OSName: Microsoft Windows 10 Enterprise|C:\Windows|\Device\Harddisk0\Partition3
OtherOperationCount: 113
OtherTransferCount: 1636
PageFaults: 2378
PageFileUsage: 1936
ParentProcessId: 588
PeakPageFileUsage: 2444
PeakVirtualSize: 2203401961472
PeakWorkingSetSize: 8744
Priority: 8
PrivatePageCount: 1982464
ProcessId: 3044
QuotaNonPagedPoolUsage: 11
QuotaPagedPoolUsage: 75
QuotaPeakNonPagedPoolUsage: 13
QuotaPeakPagedPoolUsage: 77
ReadOperationCount: 0
ReadTransferCount: 0
SessionId: 0
ThreadCount: 6
UserModeTime: 0
VirtualSize: 2203397767168
WindowsVersion: 10.0.19045
WorkingSetSize: 7729152
WriteOperationCount: 0
WriteTransferCount: 0

As you can see, there's nothing that shows the architecture from that script output here either. If we are in a lower-level coding language like C++, we can use Kernel API calls such as IsWow64Process to figure out architecture of a process. If you are using hacking tools that have that low level access you need, this is when you need to get a bit creative. Let me show you what I mean.

I had a case where a tool I used was based on some vbscript code and I needed to figure out the architecture of some of the processes to exploit further. This meant that I had to investigate ways of achieving that. After a lot of exploring processes in a lab, I realized that the vbscript code (using wmi) outputted the VirtualSize of the process. This is where things got interesting.

You see, every time a process is created in Windows, it allocates memory for that process, and uses something named virtual memory to keep track of things. Simply put, virtual memory is way for the operating system to give processes their own isolated address space. VirtualSize is the amount of virtual address space reserved for a memory allocation within a process (Virtual memory, not actual memory). The nice thing about VirtualSize is that a 32-bit process has a virtual size allocated of 4 GB while a 64-bit process typically has 8 TB.

Trying to test the VirtualSize theory in my lab, I found that key takeaway was that I could differentiate based on the size of the virtual memory. If it was 4 GB and less, then it would be a 32-bit process, and if was more than 4 GB, it would be a 64-bit process.

The VirtualSize output from the vbscript is outputted in bytes so everything less than 4,294,967,296 should be a 32-bit process and everything above should be 64-bit, regardless of how much physical memory is present. There are a few exceptions to this that I have found: “Memory compression”, “Registry”, “System”, and “System idle” processes. From my understanding, there is nothing stopping someone from launching a 64-bit process with a smaller VirtualSize, but based on default behavior, this is rarely the case. I have not yet observed that in my lab or in the wild. That being said, this is of course not a 100% guarantee way of checking it but should be good enough for use in hacking adventures.  

Now, let’s write a vbscript function that shows the process architecture. This is what I came up with:

Function list_processes()
    Set objLocator = CreateObject("WbemScripting.SWbemLocator")
    Set objWMIService = objLocator.ConnectServer(".", "root\cimv2")
    Set col = objWMIService.ExecQuery ("Select Name,ProcessId,ParentProcessId,VirtualSize,ExecutablePath from Win32_Process")
    procs = "PID" & vbTab & "PPID" & vbTab & "Arch" & vbTab & "ProcessName" & vbTab & vbTab & vbTab & "Executable Path" & vbCrLf
    For Each obj in col
        if obj.VirtualSize < 4294967296 Then
            procarch = "x86"
            if obj.processid = "0" then
                procarch = "x64"
            end if
            if obj.processid = "4" then
                procarch = "x64"
            end if
            procarch = "x64"
        end if
        if obj.Name = "Memory Compression" Then
            procarch = "x64"
        end if
        if obj.Name = "Registry" Then
            procarch = "x64"
        end if
        procs = procs & obj.ProcessId & vbTab & obj.ParentProcessId & vbTab & procarch & vbTab & obj.Name & vbTab & vbTab & vbTab & obj.ExecutablePath & vbCrLf
    list_processes = procs
End Function

wscript.echo list_processes()

Upon running this code with cscript <scriptname.vbs>, you can see something like this:

I highlighted some of the x86 processes so they are easier to read. You can see on the top process that it resolves the architecture, even if it cannot read the command line since it is not allowed. This same process of differentiating on VirtualSize could be used in other scripting languages as well to achieve the same result.

What I wanted to showcase in this post was that even if you do not find a way doing something using built-in scripting methods, it does not mean that it is not possible to achieve. Hope you found this useful and learned something new.