# Introducing PsExec for Python

Over the past few months I’ve been trying to find a way that gives people more options around running commands on a Windows host remotely. Currently you have a few options available to you that enable this;

• Configure WinRM
• Bake in commands to the startup process, like a Windows answer file or AWS user data
• Use a tool like PsExec from another Windows host

The last point is where I am going to focus this blog post on, in particular I will talk about a new Python package called pypsexec.

# Why PsExec

Before I go into the what, I need to explain why I am trying to run commands like PsExec and not just use something like WinRM. Ultimately PsExec has a few advantages over these protocols/tools like;

• No custom service or agent is required on the host
• It is only reliant on Server Message Block (SMB) which is setup and enabled on all Windows hosts
• Due to the minimal dependencies, it is really simple to allow PsExec to connect remotely compared to WinRM
• It is not platform dependent, can run on local hosts or hosts in AWS or some other cloud provider
• Allows you to easily escape WinRM hell or run as the SYSTEM account (more info around WinRM limitation can be found on this blog post)

In saying that, the PsExec model does have a few disadvantages such as;

• On Windows versions older than Server 2012 or Windows 8, there is no data encryption available
• Can only authenticated with an account that is a member of the local Administrators group
• May require some relaxing of Windows’s UAC settings if not in a domain environment
• The overhead required to run a command means it is slow to start compared to WinRM

Ultimately I wanted to have an open source PsExec alternative that I can use in situations where WinRM is not available. This means I can

• Use it to run bootstrapping scripts or adhoc commands on Windows host without requiring WinRM to be setup
• Use this library to setup WinRM on newly installed hosts without the requirement of another Windows host
• Reduce the time it takes to copy files, WinRM is really slow with file transfers while SMB is designed for this process
• Satisfy my general curiosity around how PsExec works and get a better understanding of the SMB protocol

# What is it

While PsExec is the most common name or term given to this process, it is actually a set of processes that is uses builtin protocols in Windows to work. The most common one is called PsExec and written by Mark Russinovich as part of the Sysinternals package. I’ll go into more details on how the protocol works further down but ultimately it leverages SMB and RPC to start a service on a Windows host and use that to execute the desired process.

While PsExec is probably the most popular tool that works with this model, there are a few other tools out there which offer similar capabilities. These tools are;

• PAExec a free and open source replacement of PsExec and is used by pypsexec on the remote side
• RemCom another free and open source project but hasn’t been updated since 2012 and has quite a few limitations
• Impacket a Python implementation of multiple Windows protocols including the PsExec model but leverages RemCom on the remote side

There are some others out there but these are the only ones I know that work today. Unfortunately none of these tools really fit what I am looking for, the closest is Impacket but it has a few issues from my perspective. Ultimately I needed a way to run commands using the PsExec model that fit the following requirements;

• Not reliant on Windows, this rules out PsExec, PAExec, and RemCom as they use Win32 APIs to talk to the remote Windows host
• Works on the current supported versions of Windows, Impacket is ruled out as it uses RemCom which in turn doesn’t support 64-bit architectures
• Can easily integrate into Ansible, Impacket is close but doesn’t support Python 3 and would make this step difficult

In the end I decided that I needed to write some code (turned out to be a lot) in Python to fit my requirements and ultimately that ended up with 2 Python libraries, smbprotocol and pypsexec.

# Host requirements

One of the reasons I looked into using the PsExec model is because it didn’t require a lot of steps to setup on a Windows host. These are the only things that need to be done on the Windows host for this library to work;

• Enable incoming traffic through port 445 – netsh advfirewall firewall set rule name="File and Printer Sharing (SMB-In)" dir=in new enable=Yes
• The ADMIN$share is enabled – this is enabled by default • Use a account that is a member of the local Administrators group • The connection user to have a full elevated (administrative) token on a remote logon The first 3 requirements are quite simple to set up and can either be done using sysprep images or through things like an Windows answer file during the setup. The last requirement is probably the biggest stumbling block when it comes to using this tool. To understand this restriction you first need to understand logon tokens and how they work from Windows Vista and onwards. A logon token contains the rights and groups of the account during the initial logon and since Windows Vista, the token only contains the rights of a limited user account regardless if they are an administrator. You can still run processes with the full administrative rights that is granted to the user but it must go through UAC to elevate the token (Right click -> Run as Administrator). When running things remotely, there is no GUI to right click and say Run as Administrator or any UAC prompt when the process asked for admin rights so it will fail. We need admin rights to be able to open SCMR and manage the service that will run our process. Ultimately for this to work we need the Windows host to not filter a remote logon token and this can be done through multiple ways; • In a domain environment, use any domain account that is a member of the local Administrators group • Any local Administrator will work if LocalAccountTokenFilterPolicy is set to 1 which disables the filtering • Use the builtin Administrator account (SID S-1-5-21-*-500), this account is typically disabled on desktop variants and this only works if AdminApprovalMode is not Enabled – this is not Enabled by default • For local accounts, any local Administrator will work if EnableLUA is not Enabled – this is Enabled by default • Disable UAC entirely (please don’t do this) As you can see, a domain environment makes this simple as Windows will automatically use the full elevated token on a remote logon which satisfies our requirements. If using a local account, either the initial builtin Administrator account (without AdminApprovalMode) being enabled or another local admin account with LocalAccountTokenFilterPolicy being set to 1 will work. Disabling the EnableLUA option will also work but it also affects local processes and runs them under the full token by default, effectively bypassing UAC in those scenarios. To disable the LocalAccountTokenFilterPolicy, you can run the following PowerShell script;$reg_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"
$reg_prop_name = "LocalAccountTokenFilterPolicy"$reg_key = Get-Item -Path $reg_path$reg_prop = $reg_key.GetValue($reg_prop_name)
if ($null -ne$reg_prop) {
Remove-ItemProperty -Path $reg_path -Name$reg_prop_name
}

New-ItemProperty -Path $reg_path -Name$reg_prop_name -Value 1 -PropertyType DWord

I will have to warn you, this can have some security implications for your Windows host so make sure you are aware of the risks and don’t follow the instructions blindly.

# Using pypsexec

Now onto the fun part, getting it to run a command. The first step is to have a working Python install, you can run this on Python 2.6, 2.7, 3.4 and newer. To install the pypsexec library, simply run pip install pypsexec and it will be installed for you.

One simple command and it’s done

Once installed we need to create a simple Python script to call the library and tell it what commands to run. This is a very basic template you can use which runs the command whoami.exe /all under a specific account.

from pypsexec.client import Client

server = "server2016.domain.local"
executable = "whoami.exe"
arguments = "/all"

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])

From there I can simply run the Python script with just python psexec.py and here is the result from one of my servers

Output is just like it is locally

This is a basic example of running a process but pypsexec gives you control over multiple options like;

• The number of processors it can run on
• Whether to run the command asynchronously and not wait for a response, it will continue to run in the background
• Whether to run it as an interactive process and what session to show that process on (the application will run on that session’s desktop)
• Running as a different account than what was used in the connection process
• Running as the local SYSTEM account to get godlike privileges on the process
• Change the working directory
• Set the priority of the process
• Send bytes through the stdin pipe in case the remote process requires input
• Set a timeout on the remote process

All these options and more can be found on the pypsexec Github page.

One extra feature that is not included by default is the ability to use Kerberos to authenticate and run a process as that user. This requires some Kerberos bindings to be installed on the host as well as the Python Kerberos packages. The system Kerberos bindings are dependent on the distro that is being used and once installed, needs to be configured. The Python packages can be installed by running pip install smbprotocol[kerberos]. This means that in the SMB authentication process, it will automatically attempt to authenticate with Kerberos if possible and continue on from there.

As I mentioned earlier, one of the reasons why I wanted to do this work was to add in a new way to run commands on a Windows host through Ansible. While, as of writing this blog post, it hasn’t been merged into the Ansible repository I have created a PR that you can start using today and try it out. This PR can be found here and any tests or feedback is greatly appreciated. To use this module in your own Ansible setup you will have to;

• Install pypsexec and optional Kerberos dependencies as per usual on the host the module will run on
• Copy down the psexec.py file from that PR into a folder called library that is adjacent to your playbook or in a role directory

There are multiple examples in that PR on how you can use it but I will show you a simple example like the one above;

- name: run a simple command over the psexec module
hosts: localhost
gather_facts: no
- name: run whoami /all on a Windows host
psexec:
hostname: server2016.domain.local
executable: whoami.exe
arguments: /all
register: whoami_output
failed_when: whoami_output.rc not in [0, 1]

- name: output stdout from psexec process
debug:
var: whoami_output.stdout_lines

Here is what it looks like when run;

One of the benefits I spoke about of using this module is that you can provision a Windows host and use pypsexec to provision the WinRM listeners so Ansible can communicate with it normally. With this module you can easily do this by adding in the following task

psexec:
hostname: windows-pc
executable: powershell.exe
arguments: '-'
stdin: |
$ErrorActionPreference = "Stop"$sec_protocols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
$sec_protocols =$sec_protocols -bor [Net.SecurityProtocolType]::Tls12
[Net.ServicePointManager]::SecurityProtocol = $sec_protocols$url = "https://github.com/ansible/ansible/raw/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
Invoke-Expression ((New-Object Net.WebClient).DownloadString($url)) exit This will run a PowerShell that we pass in through the stdin option that downloads Ansible’s ConfigureRemotingForAnsible.ps1 and runs it on that Windows host. Once complete, your Ansible playbook can switch over to using the standard WinRM listener and continue as usual. The next steps from here would be to look into turning this into a connection plugin within Ansible so that you aren’t limited to running commands but you can do things like copy and fetch files to the host as well as use all the Ansible PowerShell modules. This isn’t the be all to end all as the overhead required to run the process would make this quite slow and impractical to use over WinRM. # How it works Now we know how to install and run this, it’s time to get down into some protocol details and how it all stacks together. Here is a basic process flow of how this all fits together. Complex but gets the job done As you can see, the majority of the network packets sent are done through SMB and in fact the RPC packets are encapsulated inside a specific SMB packet itself. The only part that is hard to describe in the process flow is the reading of the stdout and stderr pipes. What pypsexec does is runs those read requests as part of a separate thread while it is blocked waiting for the main PAExec pipe to return the process exit info. There can be multiple responses from the server during this process and these threads will continue to run until the remote process is finished. With the basic flow out of the way, let’s drill even deeper into each of the protocols that are used. ## SMB SMB standards for Server Message Block and depending on who you ask, is also known as CIFS or Samba. SMB is the actual protocol name while CIFS is an older dialect used by Microsoft historically. Samba is a suite of programs for Linux or Unix that is designed to inter operate with various Microsoft products like SMB or Active Directory. It is a protocol that is used for providing shared access to file, printer and pipes that operates on the OSI Application layer. It can send data over numerous transports; • Directly over TCP with port 445 • NetBIOS over TCP on ports 137 and 139 • Other legacy protocols like NBF, IPC/SPX We will only focus on the direct TCP transport over port 445 as that is what is most commonly used today and provided the largest packet sizes. Some key terms in used within the SMB protocol are; • Connection: Refers to the main connection to the server over TCP, this is the level in which the negotiate process occurs and there is typically one Connection per server • Session: Refers to an authenticated session of a Connection, there is typically one Session per user per server • Tree: Refers to a connected SMB share, like ADMIN$ and is run over a Session
• Open: Refers to an open handle of a file, directory, printer, or pipe. This Open can govern what rights are allowed by a file operation based on the initial Open message and is run over a Tree
• Dialect: The version of SMB that is supported and controls what features are available and in some limited scenarios, the format of a message

### Dialects

There have been numerous revisions and changes to the SMB protocol which ultimately results in a new dialect being created. The dialect controls what features are available to an SMB connection and can control what structure the messages ultimately takes. Here are some of the main SMB dialects that are still in use today

• PC NETWORK PROGRAM 1.0, LANMAN1.0, Windows for Workgroups 3.1a, LM1.2X002, LANMAN2.1, NT LM 0.12: All SMBv1 dialects and are not used at all by smbprotocol
• 2.0.0: Introduced with Server 2008 and Windows Vista
• 2.1.0: Server 2008 R2 and Windows 7
• 3.0.0: Server 2012 and Windows 8
• 3.0.2: Server 2012 R2 and Windows 8.1
• 3.1.1: Server 2016 and Windows 10

Starting with the 2.0.0 dialect, the structure of the SMB messages have remained consistent and is supported by all currently supported Windows versions. This means the benefits of supported the older SMB 1.0 dialects is quite minimal and can open a user up to more attack vectors which is something we want to avoid.

One of the biggest changes that affects end users of this project is the addition of message encryption in the 3.x dialects. This means that only Windows hosts based on Server 2012 or Windows 8 and newer support the encryption of messages sent to and from the clients. In today’s environment, this is definitely something we want to have and it is enabled by default on pypsexec.

Who knows what Microsoft will introduce in newer dialects but currently smbprotocol supports dialects 2.0.0 to 3.1.1 and most of the features in each dialect.

### Messages

There are numerous types of messages in the SMBv2 protocol which I’ll briefly explain the major ones that are in use by pypsexec;

• SMB2 Packet Header: The header of all requests and responses, it contains the metadata around the request and response
• SMB2 NEGOTIATE: Used to negotiate the capabilities of the client and the server such as the dialect and encryption setup
• SMB2 SESSION_SETUP: Used to authenticate a user and setup an SMB Session
• SMB2 TREE_CONNECT: Used to connect to a Tree/Share on the remote host
• SMB2 CREATE: Used to create an open handle to a file, directory, printer, or pipe
• SMB2 READ: Used to read bytes from a file or pipe
• SMB2 WRITE: Used to write bytes to a file or pipe
• SMB2 IOCTL: Used to issue an implementation-specific FSCTL or IOCTL command across the network like an RPC bind
• SMB2 TRANSFORM_HEADER: Used as the header for an encrypted message and can contain 1 or multiple SMB2 Packet Headers

The smbprotocol library exposes a function that can be used to create and send each of these messages based on a few input parameters. This makes it quite simple to send an SMB2 Read request by only passing in the file handle and the offset to read from. In most circumstances, a single packet is sent to the server but the SMB protocol allows compounded packets to be sent in one request.

### Authentication

One very important part of this process is to authenticate with a valid Windows account. This is most commonly done in SMB using the SPNEGO protocol to negotiate an authentication mechanism supports by both the client and the server. Currently smbprotocol can authenticate a local or domain account with either NTLM or Kerberos where Kerberos is the preferred option of the 2. The authentication process occurs straight after the negotiation response is received with the server’s SPNEGO token. This token contains a list of authentication mechanisms that are supported which smbprotocol compares against its own setup. If the Kerberos requirements are installed and setup up correctly and the remote host indicates it supports Kerberos in the SPNEGO token, smbprotocol will first attempt to authenticate with Kerberos before falling back to NTLM.

Once authenticated, both the NTLM and Kerberos protocols supply a unique session key which is then used by SMB to derive both the signing and encryption keys. The process to compute these keys are based on the dialect that was negotiated where newer dialects have a more complicated process for greater security. Once these keys are computed and an SMB Session is created, any future messages using that Session will be encrypted using that authenticated user context.

### Encryption

If the 3.0.0 or newer dialect is negotiated then SMB allows messages to be encrypted to ensure confidentiality of the data sent to and from the server. Unlike other Microsoft protocols which typically uses the GSSAPI/SSPI Wrap and Unwrap functions based on an authenticated context, SMB relies on it’s own process for encryption. Currently there are two different types of encryption that are supported in SMB;

• AES 128-bit CCM
• AES 128-bit GCM (Dialect 3.1.1 only)

In Dialect 3.1.1, the encryption cipher that is used is negotiated in the initial SMB2 NEGOTIATE message otherwise AES 128-bit CCM is used. Some servers require encryption on all shares and is set as a global setting otherwise it can be set as an individual share setting. This cannot be controlled by the client but rest assured, smbprotocol should support each scenario.

As an example, here is a Tree Connect message sent without encryption;

Here is the same message sent with encrypted;

In the message without encryption, I can easily see that I am connecting to the share \\server2016.domain.local\IPC$whereas the encryption example I cannot even see what type of SMB message is being sent. While hiding what share I am connecting to can be important, encryption becomes even more useful when reading and writing on files and pipes so that a nefarious lurker can’t see the data. ## RPC RPC stands for Remote Procedure Call and is a way of running a procedure remotely but is coded like it would when running locally. Unfortunately the whole part of calling a procedure remotely like it would be done locally is lost when it comes to this process. This is because the usual RPC layer that handles this abstraction does not support the Windows specific functions. This means that the pypsexec library needs to implement that RPC layer when calling the functions that are required. For pypsexec, we use RPC to interact with the Windows Service Control Manager Remote (SCMR) API so that we can manage the Windows service that runs our remote payload. The RPC process is as follows; • A new SMB Open is created on the IPC$ tree for the pipe svcctl
• An SMB Write packet is sent to the opened pipe that contains the DCE/RPC Bind PDU structure
• The Bind Acknowledgement response is parsed to ensure the Bind didn’t fail
• Any SCMR calls will then send an SMB IOCTL request that contains the method and parameters to invoke on the remote host
• Once all the SCMR tasks are complete, the SMB Open is closed which also closes the binding

This was a complicated protocol to understand and I only really just scratched the surface to get the Python library working with SCMR. I’m sure there are a lot of major details I am missing or misunderstood but so far it is working and I don’t have a full need to move past it.

## SCMR

SCMR stands for Service Control Manager Remote and is a protocol that is used to remotely manage Windows services. It can do things remotely like;

• Enumerate services
• Start/stop services
• Create/delete services
• Modify services
• Lots and lots more, see the MS-SCMR docs for a full list of functions available

It is run over RPC which in turn is run over SMB pipes and on a typical Windows setup, this is all abstracted with the local RPC implementation on that host. This implementation would marshal the data that is used in the function into a byte structure and send that through the network as well as parse and marshal the responses back to the client. As mentioned in the RPC section, this is unavailable on a non-Windows host and so we have to do all this work ourselves. The current pypsexec code only has to deal with 2 different types of variables, integers and strings. Integers are packed like normally in Python as a little-endian byte but strings are a bit more complex. String have a structure like

RCP string
{
Int32 ReferenceID - A unique ID to set for the string, the uniqueness is not really implemented in pypsexec and we just set it as 1 if required otherwise it is a 0 byte value
Int32 MaxCount - The numbers of chars in the Bytes field when returned by the server, this is just set to ActualCount
Int32 Offset - The offset of the Bytes value
Int32 ActualCount - The number of chars (not Bytes) of the Bytes field when encoded including the NULL terminator
Bytes Bytes - The string that is encoded as a byte string, typically this is UTF-16-LE encoded with a null terminator
Bytes Padding - The Bytes field must align to a 4-byte boundary so this is just some NULL bytes to pad the length
}

Now that the basic data marshaling is covered, pypsexec must add support for invoking the required functions in SCMR. This is done by creating a Request PDU as defined in the RCP/DCE 1.1 documentation and send that over as a FSCTL_PIPE_TRANSCEIVE SMB IOCTL Request. Each function has a particular operation number (opnum) that is set on the Request PDU and the data is just the marshaled bytes of the function’s input parameters. The response contains at least the return code that identifies the result of the invocation and can also contain other return values based on the function that was called.

As an example, let’s dive into the ROpenServiceW function and show what happens with the data being passed to and from the client. The function is defined in MS-SCMR as;

DWORD ROpenServiceW(
[in] SC_RPC_HANDLE hSCManager,
[in, string, range(0, SC_MAX_NAME_LENGTH)]
wchar_t* lpServiceName,
[in] DWORD dwDesiredAccess,
[out] LPSC_RPC_HANDLE lpServiceHandle
);

This means it takes in 3 input parameters hSCManager, lpServiceName, dwDesiredAccess and return 2 values; lpServiceHandle and a DWORD/Int32 that indicates the function result. If we wanted to open a handle to the service called Test Service with the rights to query, start, and stop a service here is what it would look like;

The hSCManager was created as a unique handle as part of a previous call to SCMR, in this example we will just pretend it is 20 bytes of 0xFF. The string Test Service does not need a unique/referent ID and the marshaled string structure would look like

MaxCount: 0D 00 00 00
Offset: 00 00 00 00
ActualCount: 0D 00 00 00
Bytes: 54 00 65 00 73 00 74 00 20 00 53 00 65 00 72 00 76 00 69 00 63 00 65 00 00 00

The MaxCount and ActualCount is equal to 0x0D which is 13 in decimal form while the string is encoded as a UTF-16-LE string with a null terminator. Because the UTF-16-LE encoded string is 26 bytes long, we need to pad it with 2 null bytes so it is aligned to the 4-byte boundary.

The dwDesiredAccess requires 3 flags that are set to get the query, start, and stop rights which are;

• SERVICE_QUERY_STATUS: 0x00000004
• SERVICE_START: 0x00000010
• SERVICE_STOP: 0x00000020

When combined this results in an integer of 52 and the packed bytes value for this is 34 00 00 00. Putting this all together, the byte structure that is sent with the RPC Request PDU is;

FF FF FF FF
FF FF FF FF
FF FF FF FF
FF FF FF FF
FF FF FF FF
0D 00 00 00
00 00 00 00
0D 00 00 00
54 00 65 00
73 00 74 00
20 00 53 00
65 00 72 00
76 00 69 00
63 00 65 00
00 00 00 00
34 00 00 00

When sent with the RPC Request PDU, the opnum is set to 16 and that packed as a Int16 is 10 00. The server would then receive the request, unpack the data and execute the function and finally return the result under an RPC Response PDU. This response would look like;

AA AA AA AA
AA AA AA AA
AA AA AA AA
AA AA AA AA
AA AA AA AA
00 00 00 00

The first 20 bytes is the handle for the service Test Service, in this case is 20 bytes of 0xAA while the return code is 0 which means it was successful.

## PAExec

While all this is done on the client side, we still need some executable to run as the Windows service and execute the requested process. Unfortunately while PsExec is free it is not licensed for distribution which means I can’t legally use the PsExec executable to run as the service payload. In comes PAExec which is an open source alternative and can be distributed in other projects without a fee. PAExec is designed to be a drop in replacement to PsExec and offers the same features as well as some others that PsExec doesn’t supply and for this project I am just using the service component. If you are interested in delving into the code for PAExec you can find it on its own Github page.

The process around PAExec is;

• Copy the PAExec payload to C:\Windows\PAExec-<localhostname>-<localpid>.exe
• Create a service that calls C:\Windows\PAExec-<localhostname>-<localpid>.exe -service to start the PAExec service
• This will create a Named Pipe on the remote host called PAExec-<localhostname>-<localpid>.exe
• Send the PAExec process settings (contains info such as the executable to run) to that Named Pipe
• Send the PAExec start message to the main Named Pipe
• PAExec will start the process and create three more Named Pipes, PaExecOut<localhostname><localpid>, PaExecErr<localhostname><localpid>, PaExecIn<localhostname><localpid> which is the stdout, stderr and stdin for the remote process
• The client will create a separate thread for both the stdout and stderr and continually read from that pipe
• The client will also create a read request for the main Named Pipe to get the process output
• This read on the main Named Pipe will wait until the remote process finishes, during this time the stdout and stderr thread will have stored each read response in a buffer
• Now the process has ended, the client has the stdout and stderr bytes as well as the return code from the remote process.

This can be repeated multiple times and once everything is run, the client can then cleanup the payload and service that remains on the Windows host. One of the biggest challenges I faced when creating this process was how to read from the stdout and stderr pipe as the main named pipe will not complete until those buffers are read and empty. To ensure we don’t block any process in case there is no more data to be read from the pipes. What happens now is that the stdout and stderr pipes will keep on polling the remote pipe until it has been closed (the process is finished) as a separate thread. There is bound to be some more optimisations that can occur in this space but for a 1.0 product it works for me.

The biggest downfall of PAExec is that the initial settings end over the main Named Pipe is not encrypted but just encoded using a simple XOR pass. This means for Windows hosts that cannot use SMB encryption, any of these details can be viewed by anyone.

# For the future

Currently the pypsexec and smbprotocol gives you the ability to run a process on a Windows host but it is not perfect. In the future I am looking to add in the following features;

• I still occasionally see some deadlocks when running a command, this needs to be solved but it is quite hard to debug and reproduce on demand
• An interactive shell for pypsexec that takes input from stdin and outputs the responses to stdout and stderr
• Simple script bundled with pypsexec to take in arguments and run the command like the PsExec.exe executable
• Add a higher level API to smbprotocol that does things like create/deleting files or reading/writing data to a file using simple functions
• Find ways to improve the speed of smbprotocol
• Move beyond a simple Ansible module and turn it into a connection plugin so it can be used to run all the existing Windows modules instead of just commands

Some of this stuff is easy to do but others are quite complex and depend on other things to be in place to be considered viable. If you feel up to the challenge or just come across a bug, feel free to submit an issue or a pull request on Github.

1. Hi, Jordan.
I’m trying to use this library for my test automation.
Could you please point me, how to run a batch file on remote computer?
Thanks.

• jborean93

Hi

Running a batch file should be as simple as specifying the path to that file as the executable argument like so

from pypsexec.client import Client

c.connect()

try:
c.create_service()

stdout, stderr, rc = c.run_executable(r"C:\path\to\batch.bat")
finally:
c.remove_service()
c.disconnect()

There are multiple arguments you can specify to control how the process is run, more info on that can be found at the README of the pypsexec project.

Thanks

Jordan

2. Virgil

Jordan, I am trying to use pypsexec and for what ever reason it is not actually running the the commands I pass to it to run on the remote system. Below is my code snip it, any ideas why it does not actually run them.

else:
print("What time zone was that?")
return

def main():
file_copy()
install = “c:\nocprinttool\install.bat”
connection(install)
timez()
shutil.rmtree(“\\” + computer + “\c$\nocprinttool”) return def connection(app): user = os.getenv(‘UserName’) c = Client(computer, username=user, password=passwd) c.connect() try: c.create_service() c.run_executable(app) finally: c.cleanup() c.disconnect() return • Jordan Borean I’m not sure, you should store the output from run_executable and look at what was returned, e.g. stdout, stderr, rc = run_executable(app) print(stdout) print(stderr) print(rc) • Virgil Jordan, I got it to run the batch command but when trying to run a system command such as tzutil.exe, it returns unable to file the file even though I have specified the full path to the file. which is c:\Windows\System32\? Anythoughts why it is not running the command. I set python to print stdout but it fails before it prints the stdout. It does return the following in the traceback: pypsexec.exceptions.PAExecException: Received exception from remote PAExec service: Failed to start “c:\Windows\System32\tzutil.exe /s “Mountain Standard Time””. The system cannot find the file specified. [Err=0x2, 2] • Jordan Borean The issue you have is that you are specifying the executable and arguments together. What happens is the value; C:\Windows\System32\tzutil.exe /s “Mountain Standard Time” is being seen the path to an executable file and not an executable with arguments. What you need to do is specify the executable and arguments separately like so stdout, stderr, rc = c.run_executable("tzutil.exe", arguments='/s "Mountain Standard Time"') This is done so you don’t have to deal with escaping issues if there is a space in the executable path and is also how the data is marshaled to the service running on the Windows host. 3. Hi, I am trying to execute command on my remote machine with run_executable. Executable and arguments are too long for the run_excutable to handle. How can i increase the buffer size? Below is the error c.run_executable(executable4, arguments=arguments4) File “/usr/local/lib/python3.6/dist-packages/pypsexec/client.py”, line 413, in run_executable exe_result.check_resp() File “/usr/local/lib/python3.6/dist-packages/pypsexec/paexec.py”, line 108, in check_resp raise PAExecException(msg_id, self[‘buffer’].get_value()) pypsexec.exceptions.PAExecException: Received exception from remote PAExec service: Failed to start “MOVE /Y” C:\Tomcat8048_atlasprd\apache-tomcat-8.0.48\bin\tomcat8.exe....\logs* C:\Tomcat8048_atlasprd\apache-tomcat-8.0.48\bin\tomcat8.exe....\logs\oldlogs. The system cannot find the file specified. [Err=0x2, 2] • Jordan Borean Without seeing the source of the variables, it seems like executable4 is MOVE which is not a valid executable but part of cmd.exe. You would need to set the executable to cmd.exe and the arguments to /c MOVE ... to run a cmd command. So the error is correct in saying it cannot find the file (executable MOVE) specified. • Thank You so much Jordan it worked. One more issue i am receiving timed out errors from PAExec Pipe. How ever script is getting executed as required. How can avoid these errors? //usr/local/lib/python3.6/dist-packages/pypsexec/pipe.py:233: TheadCloseTimeoutWarning: Timeout while waiting for pipe thread to close: PaExecOutvinfraubuntu22204 % self.name, TheadCloseTimeoutWarning) Exception in thread PaExecOutvinfraubuntu22204: Traceback (most recent call last): File “/usr/local/lib/python3.6/dist-packages/pypsexec/pipe.py”, line 182, in run pipe_out = read_resp_func(request) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/open.py”, line 1107, in _read_response response = self.connection.receive(request, wait=wait) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/connection.py”, line 1021, in receive self._flush_message_buffer() File “/usr/local/lib/python3.6/dist-packages/smbprotocol/connection.py”, line 1128, in _flush_message_buffer message_bytes = self.transport.receive() File “/usr/local/lib/python3.6/dist-packages/smbprotocol/transport.py”, line 99, in receive packet_size_bytes = self._recv(4) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/transport.py”, line 123, in _recv raise err File “/usr/local/lib/python3.6/dist-packages/smbprotocol/transport.py”, line 117, in _recv data = self._sock.recv(buffer – len(bytes)) OSError: [Errno 9] Bad file descriptor During handling of the above exception, another exception occurred: Traceback (most recent call last): File “/usr/lib/python3.6/threading.py”, line 916, in _bootstrap_inner self.run() File “/usr/local/lib/python3.6/dist-packages/pypsexec/pipe.py”, line 204, in run self.pipe.close(get_attributes=False) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/open.py”, line 1330, in close self.tree_connect.tree_connect_id) File “/usr/local/lib/python3.6/dist-packages/smbprotocol/connection.py”, line 932, in send raise smbprotocol.exceptions.SMBException(error_msg) smbprotocol.exceptions.SMBException: Cannot find Tree with the ID 29 in the session tree table • How can i use timeout on an executable? 4. Kasper Nielsen Hello Jordan! First, awsome blog you have, really interesting reads 🙂 However im having some problems with using psexec with python, im hoping you could point me to the issue. Everyhting works fine when i run the commands manually so its not a network/permission or such issue, its something im missing with my programming. Anyway the problem, the commands im trying to run just arent being run when using python to do it for some reason? Here is a screenshot of me manually starting the calculator remotely, and the pyhthon code im trying to use to automate the same command, however, nothing happens when i run the script: https://tinyimg.io/i/RcR2tPc.png • Jordan Borean Executables started by this tool, and other network based tools like WinRM, are always started in session 0 of the Windows host whereas a local logon would be in another session. This separation is the reason why you cannot see the application running when logged on locally as the same account. Technically it may be possible for you to get it working by using the interactive and interactive_session kwarg when you call run_executable. The interactive_session kwarg takes an int value that relates to the Windows session to run the executable in. The tricky bit is finding the actual session you want to run it in. Since Vista, session 0 is reserved for services and the first interactive logon will be 1 but you could have multiple logons meaning the session is more than 1. You can determine the session of a process by running (Get-Process -Id$pid).SessionId but this would need to be run manually on your interactive logon.

5. Hi Jordan,

How do i catch below exceptions from run_executable.
My script is running without any issues but i am not able to catch these exceptions.

Traceback (most recent call last):
File “/usr/local/lib/python3.6/dist-packages/pypsexec/pipe.py”, line 182, in run
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/open.py”, line 1107, in _read_response
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/connection.py”, line 1021, in receive
self._flush_message_buffer()
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/connection.py”, line 1128, in _flush_message_buffer
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/transport.py”, line 99, in receive
packet_size_bytes = self._recv(4)
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/transport.py”, line 123, in _recv
raise err
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/transport.py”, line 117, in _recv
data = self._sock.recv(buffer – len(bytes))
OSError: [Errno 9] Bad file descriptor

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/usr/lib/python3.6/threading.py”, line 916, in _bootstrap_inner
self.run()
File “/usr/local/lib/python3.6/dist-packages/pypsexec/pipe.py”, line 204, in run
self.pipe.close(get_attributes=False)
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/open.py”, line 1330, in close
self.tree_connect.tree_connect_id)
File “/usr/local/lib/python3.6/dist-packages/smbprotocol/connection.py”, line 932, in send
raise smbprotocol.exceptions.SMBException(error_msg)
smbprotocol.exceptions.SMBException: Cannot find Tree with the ID 9 in the session tree table

• Jordan Borean

It really just depends on what threw the error but it sounds like something that shouldn’t be happening and the library need to fix. Is this consistently reproducible or does it only happen sometimes?

6. xor

I’m use ip addres. Python script it work. But ansible playbook error:

The full traceback is:
File “/tmp/ansible_psexec_payload_5kj015oj/main.py”, line 457, in main
win_client.connect(timeout=connection_timeout)
File “/usr/lib/python3.7/site-packages/pypsexec/client.py”, line 60, in connect
self.connection.connect(timeout=timeout)
File “/usr/lib/python3.7/site-packages/smbprotocol/connection.py”, line 835, in connect
self.transport.connect()
File “/usr/lib/python3.7/site-packages/smbprotocol/transport.py”, line 60, in connect
self._sock.connect((self.server, self.port))

fatal: [localhost]: FAILED! => {
“changed”: false,
“invocation”: {
“module_args”: {
“arguments”: “/all”,
“asynchronous”: false,
“connection_timeout”: 60,
“encrypt”: true,
“executable”: “whoami.exe”,
“hostname”: “10.5.5.17”,
“integrity_level”: “default”,
“interactive”: false,
“interactive_session”: 0,
“port”: 445,
“priority”: “normal”,
“process_timeout”: 0,
“show_ui_on_logon_screen”: false,
“stdin”: null,
“working_directory”: “C:\Windows\System32”
}
},
“msg”: “[Errno 113] No route to host”

• Jordan Borean

If you are running it in Ansible, make sure the module is being executed on the host you expect. The error message is saying it cannot find a route to the host you specified.

7. Dev

Hi Jordan,

Nice work, congrats.

I am trying to invoke a script written in Python and converted to exe (using pyinstaller); say file1.py to file1.exe; the file imports few other py files. This file1.exe is copied over to Machine-Local, which do not have python environment. If the file is invoked from the Machine-Local, it works. But if the same file is invoked from machine-remote, file1.exe starts executing, but fails as it couldn’t find the py files that are part of imports.
This is what I am using:
from pypsexec.client import Client

server = “192.168.0.1”
executable = “c:\folder1\file1.exe”

c.connect()
try:
c.create_service()
result = c.run_executable(executable, use_system_account=True, run_elevated=True)

finally:
c.remove_service()
c.disconnect()

The error I am getting are like:
FIle “folderOnMachine-Remote/ImportPythonFile.py” Line 8 in

Can you please let me know what could be the issue or is there something wrong I am doing.
Thanks
Dev

• Jordan Borean

Can you share the full error you are getting?

8. marius danciu

I need to run a python remotely but not running without puting it in a batch file
If I do this:
stdout, stderr, rc = c.run_executable(“c:\releaseman\UBEV\test_lcms.py”, working_dir=”c:\releaseman\UBEV”) I get the following error:
pypsexec.exceptions.PAExecException: Received exception from remote PAExec service: Failed to start “c:\releaseman\UBEV\test_lcms.py”. Unknown error value. [Err=0xC1, 193]
Note: I’ve tried to give working_dir = c:\ptyhon32 (same error)

But if I create a batch file (bat containing (python test_lcms.py):
stdout, stderr, rc = c.run_executable(“c:\releaseman\UBEV\test_lcms.bat”, working_dir=”c:\releaseman\UBEV”) — this works, no error

Could you give me a hint of how to execute a python script remotely?

• Jordan Borean

I don’t have control over the output error as that comes from PAExec. Ultimately the error you are interested in is the hex Err value 0xC1. This is actually an 4 byte integer value so just append 0 until it’s 8 characters, e.g. 0x000000C1. Having a look at https://msdn.microsoft.com/en-us/library/cc231199.aspx we can see this Win32 error code is ERROR_BAD_EXE_FORMAT which means;

%1 is not a valid Win32 application.

This library is not able to handle file extension associations so you can only invoke files that are executable like .exe, .bat, and a few others. The reason why it works in your batch file is that it is running in the cmd shell which handles file associations. You can either specify the executable as the Python.exe you want to run and have the script as the first argument, or potentially doing cmd.exe /c C:\releaseman/UBEV/test_lcms.py would also work. Not 100% confident on the last part but try it out as it should replicate what your batch file is doing.

• marius danciu

thanks a lot!, it worked using your first proposed approach… In the end I’ve managed to obtain what I needed :
stdout, stderr, rc = c.run_executable(“c:\Python32\python.exe”, arguments=’c:\releaseman\UBEV\make_connector.py -c “Unpublished Components” -b 358 -s solution -i 10.255.255.13′, username=”admin”, password=”pass”)

basically the executed script should connect back to the initiator machine and copy some folders… it worked only after provided explicitly username and password

• Jordan Borean

No worries, glad you got it working. By default an executable is run based on the connection credentials but like normal network logons you cannot authenticate to further servers like a fileshare. That’s why the username and password args were added to run_executable(). These credentials also don’t need to be the same as the connection ones, they just need to be able to log onto the host in question.

9. Hi Jordan

I am using pypsexec to run some python-selenium scripts on remote windows server but it seems that the screen resolution in this rpc is very small as compared my actual screen. and therefore my selenium scripts are failing can you help me with some soloution or alternatives

By the way Amazing Post, Keep up the good Work.

• Jordan Borean

Sorry I’m not too sure what you can do, I haven’t had to deal with screen buffers and other pseudo interactive components. A lot of the logic to actually spawn the process is through PAExec, all pypsexec does is to copy the binary, start the service, send input to start the process, and finally read the output.

10. Chandan

Hi,
I have couple of queries:
1. How to disable Debig log message on console:
07-May-2019 05:33:39 PM – smbprotocol.tree – 269 – None – INFO – Session: Administrator, Tree
07-May-2019 05:33:39 PM – smbprotocol.tree – 273 – None – DEBUG – SMB2TreeDisconnect:

How to execute powershell commands using this module ?

• Jordan Borean

For the PowerShell question, you need to call powershell.exe and target the file or commands you want to run just as you would execute it locally. For the 2nd question it sounds like you are enabled logging somewhere. The smbprotocol and pypsexec library by default does not enable these logs. You would need to track down that source and either filter out the libraries you want or make your logging mechanism a bit more targeted.

11. Chandan