10/19/2008

The great PsExec and How it works

  Recently, one of my project tasks is to remotely manage serveral tens of computers - format the disks, deploy some softwares, run some executables, copy some data files into a center place ...

  Due to the lack of cluster managment tool, I turn to a simple but very powerful tool PsExec, one of the PsTool commands from systeminternals.

  You can use this tool to execute any commands on remote nodes and bring the console output back to the node where PsExec runs. You can also copy some local file to remote node and execute that file. And also, you can specify in what user account, those commands will run. You can try that command to get more info about it.

  One thing needs to mention is that if you want to use some commands, that are inner command (for example, dir c:\*) of windows console shell(cmd.exe), you'd better tell PsExec to run "cmd /c dir c:\*" on remote server. This is because there is no executable called "dir.exe", but "cmd.exe" does.

  The most magic character of PsExec is that, there is nearly NO ANY need for the server side, no need to manually copy something into remote server, no need to open the telnet service ... So, as a developer, I think many people would ask how did PsExec do all the magic stuff? Of cause, I am one of them.

  After some discussion with colleagues and searching on the web, here is the secret:
"  First of all I've found that psexec.exe contains embedded binary resource PSEXESVC, that is actually a PE-executable, more exact it's a Win32 service. Some initial reversing of PSEXESVC discovered that this is a server part of utility responsible for starting processes and redirecting I/O to/from client system. However, lets start from very beginning and describe what psexec.exe do in sequence.

  As it expected first things utility do is checking host operating system and parameters validity, an example if the application to copy and execute exists on the host system. I think here is no need to describe this part in details, any programmer working over console application do the same things (again and again in endless loop...).

  After parameters validated psexec obtains pointer and size of PSEXESVC resource:

HRSRC hSvc = FindResource (NULL, "PSEXESVC", "BINRES" );
if ( !hSvc ) return 0;
HGLOBAL hGbDesc = LoadResource (NULL, hSvc);
DWORD dwSvcSize = SizeofResource (NULL, hSvc);
PBYTE pPsExeSvc = LockResource (hGbDesc);

  Then creates file in "\\RemoteSystemName\ADMIN$\System32" named PSEXESVC.EXE and saves resource into it. If there is no existing session with permissions to access RemoteAdmin(ADMIN$) share it tries to establish new session using username and password specified in command line via call to WNetAddConnection2 as following:

DWORD PsExecRemoteLogon (
LPCSTR lpComputerName,
LPCSTR lpUserName,
LPCSTR lpPassword
)
{
char szFullPath [_MAX_PATH];
NETRESOURCE NetResource;
sprintf (szFullPath, "\\\\%s\\IPC$");
// Initialize NetResource structure, omitted here
...
return (NO_ERROR ==
WNetAddConnection2 (
&NetResource,
lpPassword,
lpUserName,
0)
);
}
  If no error happen we have PSEXESVC.EXE in \\SystemRoot\System32 folder on remote system. Note, if the executable to start remotely must be copied to the remote system, it will be also placed into that folder. After this psexec.exe install and start PSEXESVC service using SCM API (OpenSCManager, CreateService, StartService). Full description of these calls in source would take pretty much place, and I don't see much need to do this.

  After start PSEXESVC creates named pipe "psexecsvc", and start reading messages from it. For this moment we have server part installed and started on remote system, ready to accept command messages. All other work is typical for client/server applications (for better understanding of writing server applications I strongly recommend Jeffrey Richter, Jason D.Clark "Programming Server-Side Applications for MS Windows 2000"). So psexec.exe copies executable to start to remote system if necessary, opens psexecsvc pipe on remote host (CreateFile), fill in message structure with necessary parameters (command line arguments, username&password if specified and etc...) and sends it into \\RemoteSytem\pipe\psexecsvc (TransactNamedPipe API call).

  On receiving this message PSEXESVC creates three named pipes instances "psexecsvc-app_name-app_instance-stdin", "psexecsvc-app_name-app_instance-stdout", "psexecsvc-app_name-app_instance-stderr". As you may suspect psexec.exe connects to each of these pipes and creates separate threads to work with each one. Using console functions (GetStdHandle, ReadConsole, WriteConsole and etc..) standard I/O streams (input, output, error) redirected to/from remote system through previously mentioned named pipes.

  On exiting application psexec.exe stops and uninstall PSEXESVC service, removes it's binary from remote host and removes console executable if it was copied.

  As a result you have telnet like application with extensive use of Windows NT/2000 features, it can be effectively used by system administrators for common administration tasks. The only hole (mentioned on utility homepage) is security: "If you omit a username the remote process runs in the same account from which you execute PsExec, but because the remote process is impersonating it will not have access to network resources on the remote system. When you specify a username the remote process executes in the account specified, and will have access to any network resources the account has access to. Note that the password is transmitted in clear text to the remote system". As you can see this tool is dangerous to use for remote administration via Internet or sometimes even in corporate network (as dangerous as telnet an example). One of the possible extension for this tool would be securing communication of psexec and psexesvc with encryption. However, in combination with IPSEC (if IP used as transport for CIFS) it can be successfully used even today. "


  So from the upper description we can find that the corner stones that make PsExec magic happen are two mechanisms: one is the convinient way to access "\\RemoteNode\ADMIN$" folder, withou this, PsExec can't upload the extracted PSEXESVC onto the remote server; the other one is the Remote Service Management facilities proviced by windows OS, by which PsExec can start and manage its server part. In a word, some existing inner "remote copy, remote execute" facility, provided by windows OS, make PsExec behaves like a magic tool.

  As to my task mentioned at the beginning of this article, I write some batch script and store it as *.bat file, and tell PsExec to execute it remotely. For each inner command of windows console shell, I prefix it with "cmd /c". For the powerful commands come from windows console shell and other useful executables, please refer to my previous articles. And also, use windows PowerShell and PowerShell script is another good alternative to batch script.

  One suggestion to make this tool even better is to make it have some "persistent copying" features.

  There are many situations that I need to copy some data files onto remote servers, but the user accounts in local and remote machine can't access eath other's resources, so share folder can't resolve this problem.

  Yes, PsExec now has one "-c" parameter, by which you can upload it to remote machine. But this file can only be executable files and it is the command that PSEXESVC will execute. What's more, this file will be deleted when the PsExec ends, so you can't cheat current PsExec to do what I mentioned here.

  So, in order to do "persistent copy", PsExec should either provide a switch to tell it NOT delete remote copied files on exiting, or It should provide a new parameter that is only used to copy files persistently.

No comments: