Auto-generated by GitHub Copilot. Keep this file updated as the project evolves. History: each analysis session is appended as a new dated section.
| Field | Value |
|---|---|
| Name | RemoteRun |
| Language | C# (.NET 8.0-windows), InnoSetup (ISS) |
| Framework | net8.0-windows — Windows-only, uses Win32 P/Invoke |
| Version | 1.0.7 (AssemblyVersion 1.0.7, FileVersion 1.0.7) |
| Author / Publisher | LoveDoLove |
| Repository | https://github.com/LoveDoLove/AdvancedRun-Rework |
| Solution file | RemoteRun.slnx |
| Installer script | setup.iss (InnoSetup) |
Purpose: Run arbitrary programs as NT AUTHORITY\SYSTEM on the local machine
or on a remote Windows machine, analogous to PsExec/AdvancedRun — but written from
scratch as a minimal, dependency-free .NET 8 single-file exe.
| File | Role |
|---|---|
Program.cs |
Entry point — UAC auto-elevation, argument parsing dispatch, PrintUsage() |
Options.cs |
Immutable CLI options record + stateless parser |
NativeApi.cs |
ALL Win32 P/Invoke — constants, enums, structs, delegates, DllImport |
TokenRunner.cs |
Local SYSTEM execution via token duplication (fast path) |
ServiceRunner.cs |
Install / start / wait / cleanup temporary Windows service (local + remote) |
ServiceWorker.cs |
SCM service-mode worker — reads config, executes command, writes output |
RemoteRun.csproj |
SDK-style project; AllowUnsafeBlocks=true, nullable-enabled |
setup.iss |
Dual-arch (x64/x86) InnoSetup installer |
The single binary operates in two distinct modes selected at startup:
Program.Main()
├─ IsAdministrator() → false → Elevate() via ShellExecute "runas" (UAC)
├─ Options.Parse(args)
└─ options.RemoteComputer == null?
├─ YES → RunLocal()
│ ├─ TokenRunner.TryRunAsSystem() ← fast path (no service)
│ └─ ServiceRunner.RunLocal() ← fallback
└─ NO → RunRemote()
└─ ServiceRunner.RunRemote()
Program.Main() ← args[0] == "--service"
└─ ServiceWorker.Run()
└─ StartServiceCtrlDispatcher()
└─ ServiceMain()
├─ ReportStatus(START_PENDING → RUNNING)
├─ ExecuteCommand()
│ ├─ Parse config.ini (CMDLINE, WORKDIR)
│ ├─ CreateProcess() with pipes
│ ├─ Drain stdout + stderr on threads
│ ├─ WriteOutput(output.txt)
│ └─ WriteExitCode(exitcode.txt)
└─ ReportStatus(STOPPED)
- Enable
SeDebugPrivilege,SeImpersonatePrivilege,SeAssignPrimaryTokenPrivilegeon caller's token. - Scan processes for a SYSTEM-token holder (priority order:
winlogon → services → lsass → wininit → all). OpenProcessToken→DuplicateTokenEx(primary token,TOKEN_ALL_ACCESS).CreateProcessWithTokenW— process runs as SYSTEM.
Interactive detection logic:
!Console.IsInputRedirected && !Console.IsOutputRedirected→ both stdin and stdout are real console handles → child inherits the parent console (noSTARTF_USESTDHANDLES), supports interactive programs.- Otherwise → anonymous pipes + background relay threads → suitable for scripting.
- Installs a temporary service
RemoteRunSvc_<GUID>pointing to<exePath> --service --workdir <tmpDir>. tmpDir=%TEMP%\RemoteRun_<GUID>.- Waits up to 10 s for
SERVICE_RUNNING, then waits forSERVICE_STOPPED(configurable timeout). - Reads and prints
output.txt. - Always
SafeDeleteDirectory(tmpDir)infinally.
- Verify
\\machine\admin$is reachable. - Copy
RemoteRun.exeto\\machine\admin$\RemoteRun.exe(overwrites). - Create
\\machine\admin$\RemoteRun_<GUID>\(temp dir visible as%SystemRoot%\RemoteRun_<GUID>on target). - Write
config.inito temp dir. - Service binary path on target:
%SystemRoot%\RemoteRun.exe --service --workdir "%SystemRoot%\RemoteRun_<GUID>". OpenSCManager(machine, ...)→CreateService→StartService→ wait → collect output → stop/delete.- Cleanup:
SafeDeleteDirectory(remoteDir)+File.Delete(remoteExe)— both best-effort.
| File | Written By | Read By | Content |
|---|---|---|---|
config.ini |
ServiceRunner.WriteConfig() |
ServiceWorker.ExecuteCommand() |
CMDLINE=<cmdline>\nWORKDIR=<dir> |
output.txt |
ServiceWorker.WriteOutput() |
ServiceRunner.InstallAndRun() |
merged stdout + stderr (UTF-8) |
exitcode.txt |
ServiceWorker.WriteExitCode() |
ServiceRunner.ReadExitCode() |
decimal integer |
Encoding: UTF-8 throughout. Note: legacy console programs emitting non-UTF-8 codepage output will be mis-decoded.
- State polling: 250 ms intervals using
QueryServiceStatus. - Max wait for
SERVICE_RUNNING: hardcoded 10 s. - Max wait for
SERVICE_STOPPED: fromOptions.TimeoutSeconds(default 60 s; 0 =INFINITE). - Overflow guard:
min(TimeoutSeconds, int.MaxValue/1000)before* 1000→ preventsuintoverflow.
| Group | APIs Used |
|---|---|
| SCM / Services | OpenSCManager, CreateService, OpenService, StartService, QueryServiceStatus, ControlService, DeleteService, CloseServiceHandle, StartServiceCtrlDispatcher, RegisterServiceCtrlHandler, SetServiceStatus |
| Process | OpenProcess, CreateProcess, CreateProcessWithTokenW, WaitForSingleObject, GetExitCodeProcess, CloseHandle |
| Token | OpenProcessToken, DuplicateTokenEx, GetTokenInformation, IsWellKnownSid, AdjustTokenPrivileges, LookupPrivilegeValue |
| Pipes / IO | CreatePipe, SetHandleInformation, ReadFile |
| Structs | SERVICE_STATUS, SECURITY_ATTRIBUTES, STARTUPINFO, PROCESS_INFORMATION, TOKEN_PRIVILEGES, LUID_AND_ATTRIBUTES, LUID, TOKEN_USER, SID_AND_ATTRIBUTES, SERVICE_TABLE_ENTRY |
All P/Invoke is centralized in a single static class NativeApi — no scatter across files.
RemoteRun.exe [options] [\\computer] [program [arguments]]
| Flag | Default | Meaning |
|---|---|---|
| (none) | cmd.exe |
Open interactive SYSTEM shell |
\\computer |
null | Remote target |
-w <dir> |
null (inherit) | Working directory for child process |
-d |
false | Don't wait for exit |
-t <sec> |
60 | Timeout; 0 = unlimited |
-h / --help / /? |
— | Show usage |
--service --workdir <dir> |
internal | SCM service invocation (not for users) |
dotnet build RemoteRun\RemoteRun.csproj -c Releasedotnet publish RemoteRun\RemoteRun.csproj -c Release -r win-x64 --self-contained true -o .\publish\windows-latest-x64\RemoteRun
dotnet publish RemoteRun\RemoteRun.csproj -c Release -r win-x86 --self-contained true -o .\publish\windows-latest-x86\RemoteRunISCC.exe /DMyAppArch=x64 setup.iss # → Output\RemoteRun_Setup_x64.exe
ISCC.exe /DMyAppArch=x86 setup.iss # → Output\RemoteRun_Setup_x86.exeInstaller installs:
- All published files →
{autopf}\RemoteRun\ - README.md, LICENSE
- Start Menu:
RemoteRun+System CMDshortcuts - Optional desktop shortcut (unchecked by default)
- Requires admin (
PrivilegesRequired=admin) - Post-install optional: "Open a SYSTEM command prompt now"
| # | Observation |
|---|---|
| 1 | No stdin piping to child — hStdInput = IntPtr.Zero in all code paths; interactive stdin works only via console inheritance |
| 2 | No credential passthrough — remote relies solely on the current user's Windows network credentials (NTLM/Kerberos via SMB); no -u/-p flags |
| 3 | UTF-8 assumption — output capturing always decodes as UTF-8; legacy OEM/ANSI codepage programs may produce garbled output |
| 4 | Service name conflict — uses GUID, so collision is astronomically unlikely but not validated before CreateService |
| 5 | Remote cleanup is best-effort — if the process crashes mid-cleanup, the exe and temp dir remain on the remote machine |
| 6 | No UAC manifest in project file — UAC manifest must exist at build time or elevation is done via ShellExecute "runas" at runtime (currently using runtime approach) |
| 7 | Output encoding — stdout and stderr are merged in output.txt (stdout first, then stderr) — callers cannot distinguish them |
| 8 | NoWait on service path — with -d, output is never collected (service runs detached); this is by design but undocumented in PrintUsage() |
| 9 | Interactive mode detection — `Console.IsInputRedirected |
| 10 | No timeout for StartServiceCtrlDispatcher — if SCM never calls ServiceMain, ServiceWorker.Run blocks indefinitely |
- Requires local Administrator → UAC prompted automatically.
- Remote execution requires Administrator on target machine.
- Token duplication copies the SYSTEM token from a well-known stable process; no injection.
- Temp directory is GUID-randomised; no predictable path for symlink attacks (though
%TEMP%may be user-writable on some configs). - Temp directory deleted in
finallyblock (local); remote cleanup is best-effort. - No secrets stored on disk; no credential handling.
- Service binary path uses quoted paths for spaces.