Package: pypsexec
Python PsExec LibraryThis library can run commands on a remote Windows host through Python. This
means that it can be run on any host with Python and does not require any
binaries to be present or a specific OS. It uses SMB/RPC to executable commands
in a similar fashion to the popular PsExec tool. More details on this tool
can be read onthis blog post.The executable wrapper that is sent to the service is based on thePAExeclibrary. PAExec is an free,
redistributable and open source equivalent to Microsoft'sPsExecapplication. This program is stored as a binary in this package and is used
to run the remote service and start the process execution.I would like to thank the developers of Power Admin for creating this library
as it has made this library a lot less complex than what it would have been.FeaturesWith pypsexec you can run commands of a remote Windows host like you would with
PsExec. Current you can use pypsexec to do the following;Run as a specific local or domain user or the userRun as the local SYSTEM accountRun as an interactive processSpecify the session the interactive process should run onSpecify the run level of the user token,highestorlimitedSet the priority of the processSet a timeout for the remote processSend input through the stdin pipe to the running processSet the processors the process can run onFurther InfoWhile this info is not necessary for you to use this library it can help people
understand what is happening under the hood. This library runs the following
steps when running a command;Create an SMB connection to the hostCopies across the PAExec binary to theADMIN$share of the remote hostBinds the Windows Service Manager to the openedIPC$tree using RPCCreates and starts a Windows service as theSYSTEMaccount to run the binary copiedConnect to the PAExec named pipe the service createsSends the process details to the PAExec service through the pipeSend a request to the PAExec service to start the process based on the settings sentConnect to the newly spawned process's stdout, stderr, stdin pipe (if not interactive or async)Read the stdout/stderr pipe until the process is completeGet the return code of the new processStop and remove the PAExec serviceRemove the PAExec binary from theADMIN$shareDisconnects from the SMB connectionIn the case of a failed process, the PAExec service and binary may not be
removed from the host and may need to be done manually. This is only the case
for a critical error or the cleanup functions not being called.By default the data being sent to and from the server is encrypted to stop
people listening in on the network from snooping your data. Unfortunately this
uses SMB encryption which was added in the SMB 3.x dialects so hosts running
Windows 7, Server 2008, or Server 2008 R2 will not work with encryption.This means that any data sent over the wire on these older versions of Windows
is viewable by anyone reading those packets. Any input or output of the process
comes through these packets so any secrets sent over the network won't be
encrypted. PAExec tries to reduce this risk by doing a simple XOR scramble of
the settings set inrun_executableso it isn't plaintext but it can be
decoded by someone who knows the protocol.RequirementsPython 3.6+smbprotocolTo install pypsexec, simply runpipinstallpypsexecThis will download the required packages that are required and get your
Python environment ready to do.Out of the box, pypsexec supports authenticating to a Windows host with NTLM
authentication but users in a domain environment can take advantage of Kerberos
authentication as well for added security. The Kerberos libraries are an
optional install which can be installed with;# for Debian/Ubuntu/etc:sudoapt-getinstallgccpython-devlibkrb5-dev
pipinstallsmbprotocol[kerberos]# for RHEL/CentOS/etc:sudoyuminstallgccpython-develkrb5-develkrb5-workstationpython-devel
pipinstallsmbprotocol[kerberos]Remote Host RequirementsThe goal of this package to be able to run executables on a vanilla remote
Windows host with as little setup as possible. Unfortunately there is still
some setup required to get working depending on the OS version and type
that is being used. What pypsexec requires on the host is;SMB to be up and running on the Windows port and readable from the Python hostTheADMIN$share to be enabled with read/write access of the user configuredThe above usually means the configured user is an administrator of the Windows hostAt least SMB 2 on the host (Server 2008 and newer)The connection user has a full logon token that is not filtered by UACIf connecting to localhost andpywin32is installed, the script must be run as a user with Administrator privilegesFirewall SetupBy default, Windows blocks the SMB port 445 and it needs to be opened up before
pypsexec can connect to the host. To do this run either one of the following
commands;# PowerShell (Windows 8 and Server 2012 or Newer)Set-NetFirewallRule-NameFPS-SMB-In-TCP-EnabledTrue# CMD (All OS's)netshadvfirewallfirewallsetrulename="File and Printer Sharing (SMB-In)"dir=innewenable=YesThis will open up inbound traffic to port445which is used by SMB.User Account ControlIn some circumstances, UAC will filter any remote logon token and limit the
rights that are available to it. This causes issues with pypsexec and it will
fail with anACCESS_IS_DENIEDerror message when trying to interact with the
remote SCMR API. This restriction is enforced in various different scenarios
and to get it working with pypsexec you can either;In a domain environment, use any domain account that is a member of the localAdministratorsgroupUse any local account that is a member of the localAdministratorsgroup ifLocalAccountTokenFilterPolicyis set to1This means any remote logon token will not be filtered and will have the full rights of that userBy default this is not defined and needs to be createdThis only affects remote tokens, any local tokens/processes will still be limited as per usualUse the builtin local Administrator account (SIDS-1-5-21-*-500) that is created when Windows was installedThe builtin Administrator account for English installs is typically calledAdministratorbut it can be renamedThis account is typically disabled by default on the desktop variants of Windows, e.g. Windows 7, 8.1, 10WhenAdminApprovalModeisEnabledthis will not work.AdminApprovalModeis notEnabledby defaultUse any local account that is a member of the localAdministratorsgroup ifEnableLUAisDisabledUnlike theLocalAccountTokenFilterPolicyoption, this affects local tokens and processes spawned locallyThis effectively disables UAC for any Administrator accounts and should be avoidedTo setLocalAccountTokenFilterPolicyto allow a full token on a remote logon,
run the following PowerShell commands;$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-Value1-PropertyTypeDWordTo get the name of the builtin Administrator (SIDS-1-5-21-*-500), you can
run the following PowerShell commands;Add-Type-AssemblyNameSystem.DirectoryServices.AccountManagement$principal_context=New-Object-TypeNameSystem.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine)$user_principal=New-Object-TypeNameSystem.DirectoryServices.AccountManagement.UserPrincipal($principal_context)$searcher=New-Object-TypeNameSystem.DirectoryServices.AccountManagement.PrincipalSearcher($user_principal)$users=$searcher.FindAll()|Where-Object{$_.Sid-like"*-500"}$users[0].NameThe last resort would be to disable UAC for any local Administrator account.
Once again this should be avoided as there are other options available and this
will reduce the security of your Windows host, but to do so you can run the
following PowerShell commands;$reg_path="HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System"$reg_prop_name="EnableLUA"$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-Value0-PropertyTypeDWordAfter changing theEnableLUAsetting, the Windows host needs to be rebooted
before the policies are enacted.ExamplesHere is an example of how to run a command with this libraryfrompypsexec.clientimportClient# creates an encrypted connection to the host with the username and passwordc=Client("hostname",username="username",password="password")# set encrypt=False for Windows 7, Server 2008c=Client("hostname",username="username",password="password",encrypt=False)# if Kerberos is available, this will use the default credentials in the# credential cachec=Client("hostname")# you can also tell it to use a specific Kerberos principal in the cache# without a passwordc=Client("hostname",username="username@DOMAIN.LOCAL")c.connect()try:c.create_service()# After creating the service, you can run multiple exe's without# reconnecting# run a simple cmd.exe program with argumentsstdout,stderr,rc=c.run_executable("cmd.exe",arguments="/c echo Hello World")# run whoami.exe as the SYSTEM accountstdout,stderr,rc=c.run_executable("whoami.exe",use_system_account=True)# run command asynchronously (in background), the rc is the PID of the spawned servicestdout,stderr,rc=c.run_executable("longrunning.exe",arguments="/s other args",asynchronous=True)# run whoami.exe as a specific userstdout,stderr,rc=c.run_executable("whoami",arguments="/all",username="local-user",password="password",run_elevated=True)finally:c.remove_service()c.disconnect()In the case of a fatal failure, this project may leave behind some the PAExec
payload inC:\Windowsor the service still installed. As these are uniquely
named they can build up over time. They can be manually removed but you can
also use pypsexec to cleanup them all up at once. To do this runfrompypsexec.clientimportClientc=Client("server",username="username",password="password")c.connect()c.cleanup()# this is where the magic happensc.disconnect()The script will delete any files that matchC:\Windows\PAExec-*and any
services that matchPAExec-*. For an individual run, theremove_service()function should still be used.Client OptionsWhen creating the main pypsexecClientobject there are some configuration
options that can be set to control the process. These args are;server: This needs to be set and is the host or IP address of the server to connect tousername: The username to connect with. Can beNoneifpython-gssapiis installed and a ticket has been granted in the local credential cachepassword: The password forusername. Can beNoneifpython-gssapiis installed and a ticket has been granted for the user specifiedport: Override the default port of445when connecting to the serverencrypt: Whether to encrypt the messages or not, default isTrue. Server 2008, 2008 R2 and Windows 7 hosts do not support SMB Encryption and need this to be set toFalseRun Executable OptionsWhen callingrun_executable, there are multiple kwargs that can define
how the remote process will work. These args are;executable: (string) The path to the executable to be runarguments: (string) Arguments for the executableprocessors: (list) A list of processor numbers that the process can run onasynchronous: (bool) Doesn't wait until the process is complete before returning. Thercreturned by the function is thePIDof the async process, default isFalseload_profile: (bool) Load the user's profile, default isTrueinteractive_session: (int) The session ID to display the interactive process wheninteractive=True, default is0interactive: (bool) Runs the process as an interactive process. The stdout and stderr buffers will beNoneifTrue, defaultFalserun_elevated: (bool) Whenusernameis defined, will elevated permissions, defaultFalserun_limited: (bool) Whenusernameis defined, will run the process under limited permissions, defaultFalseusername: (string) Used to run the process under a different user than the one that authenticated the SMB sessionpassword: (string) The password forusernameuse_system_account: (bool) Run the process asNT AUTHORITY\SYSTEMworking_dir: (string) The working directory of the process, defaultC:\Windows\System32show_ui_on_win_logon: (bool) Displays the UI on the Winlogon secure desktop whenuse_system_account=True, defaultFalsepriority: (pypsexec.ProcessPriority) The priority level of the process, defaultNORMAL_PRIORITY_CLASSremote_log_path: (string) A path on the remote host to log the PAExec service detailstimeout_seconds: (int) The maximum time the process can run for, default is0(no timeout)stdout: (pipe.OutputPipe) A class that implements pipe.OutputPipe that controls how the stdout output is processed and returned, will default to returning the byte string of the stdout. Is ignored wheninteractive=Trueandasynchronous=Truestderr: (pipe.OutputPipe) A class that implements pipe.OutputPipe that controls how the stderr output is processed and returned, will default to returning the byte string of the stderr. Is ignored wheninteractive=Trueandasynchronous=Truestdin: (bytes/generator) A byte string or generator that yields a byte string to send over the stdin pipe, does not work withinteractive=Trueandasynchronous=Truewow64: (bool) Set toTrueto run the executable in 32-bit mode on 64-bit systems. This does nothing on 32-bit systems, defaultFalseLoggingThis library uses the builtin Python logging library and can be used to find
out what is happening in the pypsexec process. Log messages are logged to thepypsexecnamed logger as well aspypsexec.*where*is each python script
in thepypsexecdirectory.A way to enable the logging in your scripts through code is to add the
following to the top of the script being used;importlogginglogger=logging.getLogger("pypsexec")logger.setLevel(logging.DEBUG)# set to logging.INFO if you don't want DEBUG logsch=logging.StreamHandler()ch.setLevel(logging.DEBUG)formatter=logging.Formatter('%(asctime)s-%(name)s-%(levelname)s- ''%(message)s')ch.setFormatter(formatter)logger.addHandler(ch)These logs are generally useful when debugging issues as they give you a more
step by step snapshot of what it is doing and what may be going wrong. The
debug level will also print out a human readable string of each SMB packet that
is sent out from the client but this level can get really verbose.TestingTo this module, you need to install some pre-requisites first. This can be done
by running;pipinstall-rrequirements-test.txt# you can also run tox by installing toxpipinstalltoxFrom there to run the basic tests run;py.test-v--covpypsexec--cov-reportterm-missing# or with toxtoxThere are extra tests that only run when certain environment variables are set.
To run these tests set the following variables;PYPSEXEC_SERVER: The hostname or IP to a Windows hostPYPSEXEC_USERNAME: The username to use authenticate withPYPSEXEC_PASSWORD: The password forPYPSEXEC_USERNAMEFrom there, you can just runtoxorpy.testwith these environment
variables to run the integration tests.FutureSome things I would be interested in looking at adding in the future would beAdd a Python script that can be called to run adhoc commands likePsExec.exe