Comparing Windows Execution Methods

Functionality first, security second.

The design model for Windows has traditionally focused on creating useful features. In contrast, Linux is designed for simplicity, security, and stability, Windows tries to be backwards compatible, easy to administrate, and easily integrated with other Microsoft products. These philosophies can also be seen in Microsoft's choice to store credentials of logged in users in memory. Storing credentials is necessary to allow single sign-on after a user logs in, but presents a security risk if an attacker can gain access to credential memory.

So what's the point? Windows and Active Directory have been designed with a "functionality first" approach, and often this design comes at the cost of security. It sounds like a bad thing, but this approach has largely led to Microsoft's domination of the computer market. When you log into your Windows computer at school or work, it's all but guaranteed you are authenticating against Active Directory.

Easy to administrate.

No different from other Operating Systems, Windows has the need for remote administration capabilities. During the late 1900's, our grandparents used programs like Telnet or Rlogin to perform admin tasks on their systems. Today we use SSH or Remote Desktop instead. As Windows developed, Microsoft created new methods of remote management to ease the burden on administrators -- but, for backwards compatibility reasons, they have not removed old methods. Currently, Windows computers have a plethora of remote execution methods Microsoft has collected over the years. Some of these need to be explicitly enabled, like Remote Desktop, but others are enabled by default, like SMB.

As an attacker, we have to make an important decision when we run commands on other people's computers. What execution method are we going to use? Execution methods are not created equal, and each have pros and cons, depending on the situation.

Currently, these are the legitimate methods of remote access Windows supports. Each method is covered in detail below. Unless configured otherwise, these methods require having local admin privileges on the target host.

  • Remote Desktop
  • PsExec
  • SMBexec
  • WMIexec
  • DCOMexec/MMCexec
  • Atexec
  • WinRM/PSRemoting

Lateral movement galore

Remote Desktop

Remote Desktop is a proprietary protocol that Microsoft developed to allow administrators graphical remote access to a target computer. RDP runs over TCP/3389 and must be allowed for Remote Desktop connections.

Remote Desktop Connection (mstsc.exe)

Using the Microsoft native RDP client, we can log in to systems that are set up to allow Remote Desktop.

Using the mstsc.exe client to log in
Using the mstsc.exe client to log in
Logged in to a server via mstsc.exe
Logged in to a server via mstsc.exe

 

Microsoft's Remote Desktop client is a GUI tool, and necessarily requires GUI access to use. As attackers, we do not always have GUI access, so the native Remote Desktop client is often not useful to us. The native client also does not have the option to authenticate with NTLM hashes.

XFreeRDP

XFreeRDP is a Remote Desktop client that comes with Kali Linux. It can also be used with Proxychains, making it an ideal tool in many circumstances. XFreeRDP also supports NTLM hashes instead of passwords, and can be used to pass-the-hash when RestrictedAdmin mode is not enabled.

Using XFreeRDP to log in from Kali Linux
Using XFreeRDP to log in from Kali Linux
RestrictedAdmin message when we try to pass-the-hash
RestrictedAdmin message when we try to pass-the-hash
SharpRDP

SharpRDP is a C Sharp project that provides us with the ability to run commands over RDP by sending virtual keystrokes. This is especially great when we only have access to a command line.

Running a command over RDP with SharpRDP
Running a command over RDP with SharpRDP

 

PsExec

PsExec.exe

PsExec.exe is a Windows Sysinternals tool to perform agentless remote administration by running commands remotely.

Running cmd.exe remotely on the Domain Controller
Running cmd.exe remotely on the Domain Controller

Pretty neat, but how does it work? Let's start with the network traffic.

PsExec opening up the ADMIN$ share and uploading a file named PSEXESVC.exe
PsExec opening up the ADMIN$ share and uploading a file named PSEXESVC.exe

PsExec connects to the ADMIN$ share, and creates the PSEXESVC.exe file. This uploaded file ends up in C:\Windows, and we can go look at it when a PsExec session is active. This is all SMB traffic and goes over TCP/445.

Viewing the newly uploaded EXE file
Viewing the newly uploaded EXE file

Let's see if Virustotal knows anything about this file.

Only 1 out of 73 detections -- pretty good!
Only 1 out of 73 detections -- pretty good!

Virustotal tells us this file is the "PsExec service host" -- seems reasonable considering its name. Let's look at the network traffic to see if we can find anything else.

After uploading the service host executable, PsExec contacts the Endpoint Mapper service on MSRPC/135 and negotiates a high level port for RPC communications.

EPMapper service designates our SVCCTL communication take place on TCP/49176
EPMapper service designates our SVCCTL communication take place on TCP/49176

The client can now talk to the Service Control Manager (SVCCTL) remotely over our negotiated port.

Creating and starting a new service using the Service Control Manager
Creating and starting a new service using the Service Control Manager

We can look at services on the endpoint and see that PsExec created a new service!

Viewing the new PsExec service using services.msc
Viewing the new PsExec service using services.msc

As you can see, the PSEXEC service is started, and the executable it starts is the PSEXESVC.exe file that was uploaded to the ADMIN$ share earlier.

We can see the process tree lineage -- parent and child processes of PSEXESVC.exe.

PSEXESVC.exe started by services.exe and running cmd.exe
PSEXESVC.exe started by services.exe and running cmd.exe

Let's go back to Wireshark to see if we can figure out how PsExec communication works between the two hosts.

Viewing open pipes that PsExec tries to read and write to
Viewing open pipes that PsExec tries to read and write to

The PsExec service opens three different pipes in the packet capture above: stdin, stdout, and stderr. Let's see if we can find these pipes on the host itself.

Viewing PsExec named pipes on the remote host
Viewing PsExec named pipes on the remote host

Input and output for the PsExec remote command is sent and received through these named pipes -- in our case, cmd.exe. In this way, the named pipes that PsExec creates act like the netcat -e flag does, redirecting a binary's input and output over the network.

So to summarize, PsExec:

  1. Logs into the ADMIN$ share on SMB/445
  2. Uploads the PSEXESVC.exe service binary to the share
  3. Creates a new service, PSEXESVC with executable = PSEXESVC.exe
  4. Starts the PSEXESVC service which runs the remote command
  5. Uses named pipes to send input and output to and from the remote host
  6. Stops and deletes the PSEXESVC service
  7. Deletes the PSEXESVC.exe file from ADMIN$

PsExec.exe can situationally be a good tool for attackers since it is legitimately used by Windows administrators, but it is a Windows EXE, which limits an attacker to Windows -- not ideal. If only there were a Linux version of PsExec...

psexec.py

Introducing psexec.py the Linux version of PsExec.exe. psexec.py is an Impacket script which replicates the same execution behavior explored above.

Running psexec.py from Linux to perform remote administration on Windows
Running psexec.py from Linux to perform remote administration on Windows

Notice some key differences in the screenshot above: psexec.py gives a random name to the created service and uploaded executable. Seems a little bit less legitimate than the Sysinternals PsExec.exe.

We can take a look at the executable file psexec.py creates.

Viewing Virustotal results for the RfCwREED.exe file uploaded by psexec.py
Viewing Virustotal results for the RfCwREED.exe file uploaded by psexec.py

Oh no! It looks like the binary psexec.py creates is not viewed favorably by most antivirus. Checking the source code for psexec.py, we can see the executable is from the RemCom project -- an open source replacement for PsExec.exe. We can see the similar, but slightly different named pipes opened up by the RemCom binary.

Viewing RemCom named pipes on the remote host
Viewing RemCom named pipes on the remote host

Would it be possible to get psexec.py to use the original PsExec.exe service executable? We probably could. Outside of copyright infringement, there should be no reason we can't build a Linux compatible client for the real PsExec service. But the reality of it is, PsExec is not stealthy, and is generally a bad method of execution for an attacker. There are too many artifacts that PsExec leaves, legitimate or not. A binary dropped to disk and a new service created on the remote host. Any competent blue team should be able to log on these -- especially the created service. So what should we use instead if PsExec is so bad?

 

SMBexec

Introducing SMBExec, a slightly better method to execute commands remotely. Before we talk about the tools, it's important to understand that SMBExec is a technique, not a tool -- though there are tools with the same name. Some tools that take advantage of the SMBexec method: CrackMapExec, secretsdump, smbexec.py, Invoke-SMBExecSCShell, and more.

So what's the difference between PsExec and SMBExec methods? Let's observe the network traffic of smbexec.py to figure that out.

smbexec.py

We can see Impacket's smbexec.py looks identical to psexec.py at first glance.

Executing commands via smbexec.py
Executing commands via smbexec.py

But it's a different method. Let's check the packet capture to see how.

Logging in and opening the Service Control Manager on port 445
Logging in and opening the Service Control Manager on port 445

To start it off, smbexec.py logs in to SMB, connects to the IPC$ share, which contains the svcctl named pipe -- another way to access the Service Control Manager. Remember the method standard PsExec connected to the Service Control Manager was MSRPC on TCP/135 and negotiated a high level port to talk to the Service Control Manager.

Viewing the creation and immediate deletion of a new service
Viewing the creation and immediate deletion of a new service

After opening the Service Control Manager, smbexec.py creates a new service, starts it, and then immediately deletes it. What? Let's look at the logs on our remote host to find out more about that service.

Opening up our old friend Event Viewer on the remote host
Opening up our old friend Event Viewer on the remote host

There are a few things to note here. First, event type 7045 is a creation of a new service. Notice that smbexec.py produces many of these service creation events. Every command executed using SMBexec creates a new service. This means that if you run 100 commands with SMBexec, there will be 100 events of type 7045. It also means any commands you run using SMBexec end up in someone's logs.

Additionally, output is redirected to a file named __output and placed on the C$ share. I had to make a few modifications to the code to be able to view this file because normally __output is read from a share and then immediately deleted.

Commenting out the Python code for downloading and deleting the __output file
Commenting out the Python code for downloading and deleting the __output file

Now that we removed the script's ability to delete the __output file, we should be able to see it on the C$ share.

Viewing SMBExec's __output on the C:\ drive from the whoami command
Viewing SMBExec's __output on the C:\ drive from the whoami command

The smbexec.py script also unfortunately uses static values for many of its undesirable artifacts. Every service smbexec.py creates will be named BTOBTO unless changed in the code.

Viewing static names in the smbexec.py code
Viewing static names in the smbexec.py code

To summarize, the SMBexec technique:

  1. Logs into the Service Control Manager
  2. Creates a new service with the executable being a single command
  3. Runs the new service, optionally saving command output to an SMB share
  4. Stops and removes the newly created service
  5. Repeat from step 2 for additional commands run

This leaves us with a lot of services created, command output written to disk, and all commands run end up in Windows logs. SMBExec ends up not much better than PsExec for stealth. Let's see if we can still salvage this exec method...

SCShell

SCShell is a twist on the traditional SMBExec method that modifies existing services to run commands instead of creating new services. To aid with stealth, command output is also not returned.

Running commands with the Python variant of SCShell
Running commands with the Python variant of SCShell

Going back to Wireshark, we can see that the targeted service was modified -- no services were created by SCShell.

SCShell modifying a service using ChangeServiceConfigW
SCShell modifying a service using ChangeServiceConfigW

We can view TapiSrv, the hijacked service using services.msc and confirm the executable was modified.

Viewing the mshta command ran in scshell.py
Viewing the mshta command ran in scshell.py

The data that shows up in Event Viewer is also much more terse, as apparently service modifications are not logged by default.

Viewing logs related to SCShell command execution
Viewing logs related to SCShell command execution

Downsides of SCShell include a lack of command output and the possibility for artifacts if a service isn't set back the way it was before modification.

Though no tool like this exists, I think a good compromise between stealth and usability would be:

  1. Create a new service with random or innocent-looking name
  2. Set executable to be blank or innocent-looking binary. This executable would show up in Event Viewer logs.
  3. Modify the service with desired command and start the service
  4. Save output to a randomly named file stored in %TEMP%
  5. Modify and restart the same service for additional commands
  6. Finally, delete the service when finished

Advantages of this method would be:

  • Only one service created
  • No commands stored in Event Viewer
  • Command output retrieved
  • No statically named services or files
  • No possibility of accidentally messing up a real service.

 

WMIexec

Shifting gears away from Services, WMIexec is an execution method that relies on a specific feature of the Windows Management Instrumentation (WMI). WMI was intended to be a standard that would allow software or administrators to query Windows machines for information. WMI can be used to identify the Operating System type and version, find currently logged on users, or get file contents. For some reason it can also be used to create processes on the remote host -- Ouch. Since we're talking about remote execution methods, we'll be focusing on this "feature" of WMI.

WMI is enabled on Windows by default, making it a prime candidate for lateral movement. The WMI service relies on DCOM, which is present on MSRPC (TCP/135).

From one of the creators of Impacket:

A similar approach to smbexec but executing commands through WMI. Main advantage here is it runs under the user (has to be Admin) account, not SYSTEM, plus, it doesn't generate noisy messages in the event log that smbexec.py does when creating a service. Drawback is it needs DCOM, hence, I have to be able to access DCOM ports at the target machine.
wmic.exe

The old school method for WMI lateral movement is the Windows builtin executable: wmic.exe. This WMI client was apparently deprecated in 2012, and could be removed sometime in the future. That being said, we all know Microsoft's stance on keeping antiquated parts of Windows around.

Running mshta.exe command on a remote host using wmic.exe
Running mshta.exe command on a remote host using wmic.exe

Notice how there is no command output with wmic.exe. We can, however, see the return value of 0, meaning the command ran successfully.

Running a bad command with wmic.exe
Running a bad command with wmic.exe

The above command failed, and gives us a return value of 9. If the command fails, it will show a non-zero return value.

Additionally, we can use the current logon session instead of specifying credentials.

Using wmic with the current user's session token -- no credentials required
Using wmic with the current user's session token -- no credentials required

Unfortunately, wmic.exe does not support passing-the-hash.

WMI using Powershell

The replacement for wmic.exe are Powershell cmdlets Invoke-CimMethod and Invoke-WmiMethod. We can use these cmdlets to execute commands just like we did with wmic.exe.

Using the same "process call create" method except in Invoke-WMIMethod
Using the same "process call create" method except in Invoke-WMIMethod

Like wmic.exe, we can specify alternate credentials.

Specifying credentials for Invoke-WmiMethod to use
Specifying credentials for Invoke-WmiMethod to use

We can also create a wmiclass object and use it directly. Unfortunately, I don't think it's possible to feed it alternate credentials.

Creating a wmiclass object on the Domain Controller and creating a remote process
Creating a wmiclass object on the Domain Controller and creating a remote process
WMIOps

WMIOps is an extension of the basic WMI Powershell cmdlets tailored for offensive security. It supports many functions, including WMI command execution. Here we execute an mshta.exe command on the Domain Controller.

WMIOps WMIExec with alternate credentials
WMIOps WMIExec with alternate credentials

We can also read files remotely using WMI. If we save the output from a command to a file, then read the file with WMI, we can effectively gain command output!

WMIExec with command output in two commands
WMIExec with command output in two commands
wmiexec.py

Surprise! Impacket has an example script to do WMIexec from Python platforms. wmiexec.py does exactly what it says on the tin.

Using Impacket's wmiexec.py script to run commands on a remote host
Using Impacket's wmiexec.py script to run commands on a remote host

By default, the Blue Team won't see lateral movement over WMI. No services are created, or files dropped to disk. However with some additional audit settings, we can observe the processes created from WMIexec. Put your blue team hat on for this next part.

First, we need to enable auditing for Process Creation. This allows us to see Event logs with ID 4688.

Enabling audits for process creation using gpedit.msc
Enabling audits for process creation using gpedit.msc

Next we need to enable command line auditing for process creations. This allows us to see the full command line of created processes.

Enabling command line for processes creation using gpedit.msc
Enabling command line for processes creation using gpedit.msc

Now when we go check out process creation logs for WMIExec, we can pretty clearly see the evil that is happening on our system.

Viewing the evil that now shows up in our event logs
Viewing the evil that now shows up in our event logs

That's enough Blue Teaming for now. Since the detections above are non-default, WMIexec is miles ahead of PsExec or SMBexec in terms of stealth. 

 

DCOMexec/MMCexec

Rather than trying to explain how DCOMexec works myself, I'm going to defer to the original blog post regarding this discovery. In essence, through the use of remote COM objects, it is possible to execute commands remotely through the MMC20 application.

Quoting Matt Nelson's blog post:

While enumerating the different DCOM applications, I came across the MMC Application Class (MMC20.Application). This COM object allows you to script components of MMC snap-in operations. While enumerating the different methods and properties within this COM object, I noticed that there is a method named “ExecuteShellCommand” under Document.ActiveView.

Read the full blog post to see manual DCOM execution. Impacket also has a script to establish a DCOM shell.

Running command using Impacket's dcomexec.py script
Running command using Impacket's dcomexec.py script

As Matt Nelson's blog post states, this technique is blocked by the Windows firewall by default. The firewall on the target host was disabled for dcomexec.py to work.

We can see legitimate behavior of MMC used to manage remote systems here.

Using mmc.exe to create a new local admin on the remote host
Using mmc.exe to create a new local admin on the remote host

Though not a true "remote access" method yet, we can see that MMC has the capability to remotely interact with another machine's administrative facilities.

This is where I'm going to leave this exec method. There are a couple of different object types we could use instead of MMC20, but none of them worked for me unless the firewall was disabled. It's a cool proof of concept, but has never been useful to me during client engagements.

 

Atexec

The Atexec method uses the Windows Task Scheduler to run commands remotely. We can create a new immediate scheduled task and have it delete itself after it completes.

Task Scheduler

With the correct privileges, Task Scheduler supports interacting with tasks on remote computers.

Task Scheduler option to allow configuring tasks on a remote computer
Task Scheduler option to allow configuring tasks on a remote computer

We can create a new scheduled task that runs immediately on the target computer. Right click the Task Scheduler Library and select "create task".

Inputting the command for the task to run
Inputting the command for the task to run
Setting the task trigger to be "immediate"
Setting the task trigger to be "immediate"
Changing the task to delete itself after it's finished
Changing the task to delete itself after it's finished

As soon as we click "OK" the scheduled task we just made executes and then deletes itself. We can see that the task command ran in our web logs.

Viewing the web request made by the mshta command
Viewing the web request made by the mshta command

We do not get command output from this manual method -- it is blind execution.

Schtasks.exe

We can create the same immediate tasks with the command line tool schtasks.exe. This command sucks, but it's still good to have options. Instead of creating a task that deletes itself when finished, we need to create a new task, manually run the task, then manually delete it.

set RAND=%RANDOM% & schtasks.exe /Create /S %TARGET% /U %USER% /P %PASS% /TN %RAND% /SC MONTHLY /TR %COMMAND% & schtasks.exe /Run /S %TARGET% /U %USER% /P %PASS% /TN %RAND% & schtasks.exe /Delete /S %TARGET% /U %USER% /P %PASS% /TN %RAND% /F

If desired, omit the /U %USER% and /P %PASS% to use the current user's session token.

New-ScheduledTask

Instead, we can use the New-ScheduledTask family of Powershell cmdlets. The following stacked command creates a new task, starts it, then deletes it.

$Rand = Get-Random; $Cim = New-CimSession -Credential (new-object PSCredential $User,(ConvertTo-SecureString -AsPlainText -Force $Pass)) -SessionOption (New-CimSessionOption -Protocol Dcom) -ComputerName $Target; Register-ScheduledTask -TaskName $Rand -Action (New-ScheduledTaskAction -Execute $Command) -Trigger (New-ScheduledTaskTrigger -Once -At (Get-Date)) -Settings (New-ScheduledTaskSettingsSet) -CimSession $Cim | Start-ScheduledTask; Unregister-ScheduledTask -CimSession $Cim -TaskName $Rand

Note that you need to fill out or set $User, $Pass, $Target, and $Command before pasting this into Powershell. Here's an example of the command in use:

Using Powershell scheduled tasks to spawn an Empire agent on the Domain Controller
Using Powershell scheduled tasks to spawn an Empire agent on the Domain Controller

Our lateral movement command executes successfully. We get a web request from mshta.exe, and our payload is executed on the remote host.

Empire session opening from previous scheduled task command
Empire session opening from previous scheduled task command

Note that even though though the scheduled task is deleted, our desired lateral movement process is not terminated.

atexec.py

Finally, Impacket has a python script that is similar to the other Impacket exec scripts, but limited to running one command at a time. The script operates in a similar create, run, delete fashion as the Powershell above.

Running a command with atexec.py
Running a command with atexec.py

 

WinRM and PSRemoting

WinRM is the last execution method covered in this already very long blog post. PSRemoting is included here because it relies upon the WinRM service to run commands -- no WinRM, no PSRemoting. I should also note that WinRM is not enabled by default. This already makes WinRM less useful than other methods. At best, it is a niche execution method that can be used during some engagements. If hosts have TCP/5985 or TCP/5986, it's a likely bet they have WinRM enabled.

These ports, if scanned with a port/service/vulnerability scanner like Nessus, will show up as web service ports, because WinRM actually uses SOAP, a web based protocol to perform management under the hood.

The first thing we need to do to test out WinRM is to enable it on an endpoint. We can either use winrm.exe or Enable-PSRemoting. Both of these require local administrator privileges.

Enabling WinRM using winrm.exe
Enabling WinRM using winrm.exe

Note here that the -q flag will answer yes to all [Y/n] prompts that would normally be shown without the flag.

Enabling WinRM using Enable-PSRemoting
Enabling WinRM using Enable-PSRemoting

Once WinRM is enabled, we can use tools to execute commands remotely.

winrs.exe

The first tool is winrs.exe, a builtin Windows binary client for WinRM.

Executing a command remotely using winrs.exe
Executing a command remotely using winrs.exe

Like other legitimate Microsoft tools, we can authenticate with the current user's session or alternate plaintext credentials, but not NTLM hashes.

PSRemoting

Powershell comes with a cmdlet to run remote Powershell on WinRM enabled computers.

Running commands on the Domain Controller using Enter-PSSession with alternate credentials
Running commands on the Domain Controller using Enter-PSSession with alternate credentials

If interactive isn't your style, single commands can be run using Invoke-Command.

Running two commands on the Domain Controller using Invoke-Command
Running two commands on the Domain Controller using Invoke-Command
Evil-WinRM

If Powershell isn't available on your system, or you need to pass-the-hash, Evil-WinRM has your back. Evil-WinRM is a WinRM/PSRemoting client written in Ruby intended for penetration testers.

Logging into a workstation with evil-winrm
Logging into a workstation with evil-winrm

It has a lot of nice features like pass-the-hash and supports loading Powershell scripts and C Sharp executables on the fly.

Passing the hash and viewing available modules with evil-winrm
Passing the hash and viewing available modules with evil-winrm

 

Epilogue

While each execution method has their own situational strengths and weaknesses, there is a general order I follow when attempting lateral movement:

  1. WMIexec
  2. WinRM (When available)
  3. SMBexec
  4. PsExec
  5. Remote Desktop (When available)
  6. Atexec
  7. DCOMexec

The list above is a good start, but consider the stealth/opsec requirements for every situation before moving laterally.

This article was updated on July 11, 2022