PyPsExec for Remote Command Execution

Voyag3r
7 min readAug 31, 2024

--

Introduction

In one of my previous posts I took a look at the inner workings of Impacket’s PsExec and SMBExec modules, and as part of that research I came across a Python implemention of PsExec. Written by jborean93 and hosted on Github, the project called PyPsExec makes use of the PAExec executable from PowerAdmin which is a free and open-source alternative to Microsoft’s PsExec application. In one of the rabbit holes I fell into during that research, I was trying to make impacket-psexec run without tripping Windows Defender, and so when I came across PyPsExec I was interested to see how it would fair against Windows Defender out of the box. So after I concluded my look at the Impacket tools, I turned my attention here.

PyPsExec Execution

While reading through the documentation for this tool, the first thing that caught my attention that was different from the other tools I was looking at is that PyPsExec is a library rather than a packaged tool. This means that in order to run it, you have to put in some elbow-grease (or knuckle-grease, in this case) and write your own Python script to call the library and implement the functions. Luckily, jborean93 provides a simple example script in his introductory blog post to the library that demonstrates how to call on its functions to connect to the victim machine and run the commands.

from pypsexec.client import Client

server = "192.168.0.125"
username = "admin"
password = "**********"
executable = "whoami.exe"
arguments = "/all"

c = Client(server, username=username, password=password,
encrypt=True)

c.connect()
try:
c.create_service()
result = c.run_executable(executable, arguments=arguments)
finally:
c.remove_service()
c.disconnect()

print("STDOUT:\n%s" % result[0].decode('utf-8') if result[0] else "")
print("STDERR:\n%s" % result[1].decode('utf-8') if result[1] else "")
print("RC: %d" % result[2])

The script is pretty simple to follow. Variables for the host, username, and password are hard-coded, as well as the executable to be called and its arguments in a separate variable. A connection to the victim machine is then established and it tries to create a service to run the executable, stores the result, then removes the service and disconnects. Running the script above results in the following output:

Successful ‘whoami’ Execution

We see we were able to successfully connect to the victim machine and run the “whoami /all” command. Now, switching over to the Windows Event Viewer we can see that it executed a similar process as impacket-psexec:

Windows Event Viewer

It transfers the file PAExec-13013-kali.exe to the C:\Windows directory on the victim machine and creates a service by the same name to call that executable. I was initially unable to find this executable as the file and the service get removed after successful execution, but after playing with it a bit more I was able to force the process to close without completion, which left the executable behind.

Transferred Binary

Obviously the file doesn’t attempt to hide itself or its purpose at all, which was interesting as it didn’t trip Windows Defender. Once again turning to Process Monitor from Sysinternals, I ran the command again and took a look.

PAExec Under Process Monitor

Similar to PsExec, it creates a lot of noise — loading images, creating files, and interacting with registry keys. I was surprised that I was able to run this with Windows Defender enabled without any issues, and so I computed the hash of the executable and ran it through VirusTotal.

PAExec VirusTotal

Only 7 vendors identified PAExec as malicious, while over 60 had identified the PsExec executable from Impacket as malicious. That helps explain a bit why Windows Defender didn’t immediately quarantine it — it appears to be treated as a legitimate executable rather than commonly considered malware. Once again falling into a bit of a rabbit hole, I wanted to see if I could replace the PAExec executable with my own and maintain functionality. Thanks to the practice I’d had trying to compile the RemCom binary, I was able to quickly compile my own PAExec binary from the PowerAdmin Github and replace the default binary with it.

Replacing the Default PAExec Binary

Surprisingly, it worked on the first try exactly like the default binary had. Computing the hash and running it through VirusTotal showed that no vendors flagged it as malicious, though of course that just means the hash of the newly compiled binary just hasn’t yet been flagged by those vendors. It’s possible (likely even) that if I uploaded the binary itself to VirusTotal and it had the chance to analyze the binary completely it would still get marked as malicious.

Now that I had a better understanding of the PyPsExec library and how it worked, I wanted to see how I could make it more usable and interactive. Running only one hardcoded command in the script is a little clunky and unwieldy. Starting from the base script I had, the major changes I wanted to make were: 1). Supplying the host, username, and password as command-line arguments, 2). Looping through the script so it didn’t immediately end upon successful execution, and 3). Supplying the executable and arguments in the terminal. After a little bit of experimentation and tinkering, I quickly ended up with the following code:

from pypsexec.client import Client
import sys

server = sys.argv[1]
username = sys.argv[2]
password = sys.argv[3]

while True:
c = Client(server, username=username, password=password,
encrypt=True)

c.connect()

executable = input("Enter the executable: ")
arguments = input("Enter the arguments: ")

try:
c.create_service()
result = c.run_executable(executable, arguments=arguments)
finally:
c.remove_service()
c.disconnect()

print("STDOUT:\n%s" % result[0].decode('utf-8') if result[0] else "")
print("STDERR:\n%s" % result[1].decode('utf-8') if result[1] else "")
print("RC: %d" % result[2])

I was now able to supply the host, username, and password in the command line, and loop through the connection to supply different commands and execute different programs without having to change the script.

Successful Semi-Interactivity

As you can see in the screenshot above, I was able to successfully execute cmd.exe to execute ‘whoami’ and ‘ipconfig’ and loop back around to wait for another command. In playing around with this semi-interactive script I was reminded that there’s often more than one way to skin a cat — depending on what I’m trying to do, I can call the executable itself and supply the arguments as I did in the original script to call whoami.exe, or I can instead utilize Powershell or the Command Prompt as I did directly above. However, this doesn’t offer quite the same interactivity as PsExec in which you can execute interactive functions. Instead, it’s a bit more similar in function to impacket-smbexec in which the command is run and then the output is sent back to your terminal, so any interactive commands you issue will just hang. When using this method, you have to be careful what you run.

Summary

This was just a quick look at PyPsExec to satisfy my curiosity as an alternative to Impacket. It functions rather similarly to PsExec in that it transfers a binary to the victim and creates a service to call that binary, then removes them both upon successful completion. Additionally, implementing it the way I did provides a semi-interactive system of executing commands on the victim in a similar way to SMBExec. The neat thing about it is it doesn’t have quite the same bad reputation as Impacket’s PsExec binary among the major Antivirus vendors, and because it’s a Python library it really leaves the door open for how you want to implement it in your script and any other tools with which you want to integrate it. However, the downside of the way I implemented it is you end up with the worst of both PsExec and SMBExec — A malicious binary gets dropped to disk on the victim and shoots off a lot of noisy processes that provides ample opportunity for detection like PsExec does, and then the 7045 event IDs get created for each command that is run like with SMBExec (not too mention again dropping the binary and running a bunch of processes). A neat little experiment, but not one of the first tools I would turn to if I’m trying to leave as little trace of my presence as possible on a system.

I’d highly recommend checking out both the PyPsExec and PAExec projects on Github, and of course, don’t use either on systems that you do not own or have explicit permission to test.

--

--