Skip to main content
  1. Posts/

PsExec: The Double-Edged Sword of Remote Execution

··884 words·5 mins·
Table of Contents

PsExec has been around since Mark Russinovich wrote it for Sysinternals in 2001. It’s signed by Microsoft, works on basically every Windows version, and still does what it claims to do. It also writes a binary to C:\Windows and registers a service called PSEXESVC — both of which show up in default detection content for every commercial EDR. So the issue isn’t whether PsExec works. It does. The issue is whether using it lets a SOC see your whole shape inside ten minutes.


1. How it actually works
#

When you run psexec \\target cmd.exe, here’s the sequence on the wire:

  1. Authentication. It connects to the target’s IPC$ share over SMB (port 445).
  2. Binary upload. It copies a service executable (PSEXESVC.exe by default) to the ADMIN$ share, which maps to C:\Windows.
  3. Service creation. It uses the Service Control Manager to register and start a service named PSEXESVC.
  4. Named pipe. The service opens \pipe\psexecsvc for redirected stdin/stdout.
  5. Execution. The service spawns your command as a child process.
  6. Cleanup. On exit, it stops and deletes the service, and deletes the binary.

Steps 2 and 3 are the problem. Disk writes plus a service install is the loudest combination Windows offers.


2. Offensive usage
#

Most of the time, “PsExec” in a red team context means Impacket’s psexec.py, not the Sysinternals binary, mostly because Impacket can authenticate with an NTLM hash directly.

Impacket and pass-the-hash
#

# domain/user@target -hashes LM:NT
psexec.py -hashes :8846f7eaee8fb117ad06bdd830b7586c administrator@10.10.1.5

You get a shell running as NT AUTHORITY\SYSTEM because the command runs through a service.

Running as SYSTEM
#

The Sysinternals binary needs -s to elevate to SYSTEM; the default is whoever you authenticated as. Impacket’s psexec.py is SYSTEM by default (because it always installs a service).

Interactive GUI session (-i)
#

If you actually want output on the user’s desktop — for a message box, a GUI tool, or shenanigans — use -i with the right session ID.

:: List sessions
qwinsta /server:10.10.1.5

:: Pop into session 2
psexec -i 2 -s \\10.10.1.5 "msg * System Maintenance in Progress"

3. Cutting the noise
#

If you’ve decided to use PsExec anyway, do not use defaults.

Rename the service (-r)
#

The default service name PSEXESVC is a static string flagged by every IDS and EDR that exists.

Sysinternals binary:

psexec -r "WinSystemHelper" \\target cmd.exe

Impacket’s psexec.py randomizes the service name (4 random ASCII letters, e.g. aBcD) and the dropped binary name (8 random letters plus .exe, e.g. KxJmRnPv.exe) by default, so it avoids the static PSEXESVC signature. But a random 8-character executable showing up in C:\Windows and immediately registering as a service is still a high-signal IOC; it’s just one your detection has to be slightly smarter to catch. Use the -service-name argument to pick something credible like AdobeUpdateService.

File-less-ish alternatives
#

If you want PsExec’s feel without the binary drop, use the rest of the Impacket execution family:

  • smbexec.py — doesn’t upload a binary. It creates a service whose binPath is cmd.exe /c <command>, redirects output to a temp file under %SystemRoot%, then reads the file back over SMB. No EXE on disk, but you still get a service.
  • wmiexec.py — uses WMI’s Win32_Process.Create over DCOM (port 135 + dynamic high port). No service, no dropped binary. Of the three, this is the quietest.

There’s a tradeoff. wmiexec.py runs commands one at a time with no real PTY, so anything interactive — text editors, sudo prompts, anything that wants a real terminal — is awkward at best.

Named-pipe evasion
#

EDRs increasingly hook named-pipe creation. Tools like SharpNoPSExec, and modified Impacket forks, let you change the pipe name to something that blends with normal Windows traffic: \pipe\atsvc (Task Scheduler), \pipe\spoolss (Print Spooler / MS-RPRN), \pipe\samr, \pipe\lsarpc. These all see legitimate use, so the pipe itself doesn’t trigger anything; the parent-child process tree might still.


4. Forensic artifacts
#

If a defender looks, they will find you.

  1. Event ID 7045 (new service installed) and 7036 (service state change). Both go to the System log on the target.
  2. File system. PSEXESVC.exe (or the random Impacket name) lives in C:\Windows while the service is running. Even after deletion, MFT entries and USN Journal records persist.
  3. Prefetch. The dropped binary gets a Prefetch entry on execution.
  4. Shimcache / Amcache. Windows tracks the binary’s hash and path here for app-compat purposes.
  5. Named pipes. \pipe\psexecsvc (or your renamed pipe) is observable from SMB packet capture.
  6. EULA registry key. HKCU\Software\Sysinternals\PsExec\EulaAccepted is set the first time Sysinternals PsExec runs on a host. Its presence is direct evidence that PsExec was launched from that machine — useful if you’re the defender, painful if you’re the operator who forgot to clean a jump box.

Closing
#

PsExec is reliable and produces an interactive SYSTEM shell with one command. That alone earns it a place in the toolbox. It’s also one of the most-detected things you can do on a Windows host, so the calculation isn’t “is PsExec a good tool” — it’s whether the engagement is at a stage where you can afford to be seen. On a stealth-focused op, wmiexec.py or one of the sc.exe reconfiguration techniques will usually get you the same code execution for substantially less noise.


References
#

UncleSp1d3r
Author
UncleSp1d3r
As a computer security professional, I’m passionate about building secure systems and exploring new technologies to enhance threat detection and response capabilities. My experience with Rails development has enabled me to create efficient and scalable web applications. At the same time, my passion for learning Rust has allowed me to develop more secure and high-performance software. I’m also interested in Nim and love creating custom security tools.