Skip to content

Latest commit

 

History

History
225 lines (171 loc) · 10 KB

File metadata and controls

225 lines (171 loc) · 10 KB

RemoteRun — Project Analysis

Auto-generated by GitHub Copilot. Keep this file updated as the project evolves. History: each analysis session is appended as a new dated section.


── Session 1 · 2026-03-15 ──────────────────────────────────────────────────

1. Project Identity

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.


2. Source File Map

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

3. Architecture & Execution Modes

The single binary operates in two distinct modes selected at startup:

3a. Client Mode (normal invocation)

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

3b. Service Mode (SCM invocation)

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)

4. Execution Strategy — Local

Fast Path: TokenRunner.TryRunAsSystem()

  1. Enable SeDebugPrivilege, SeImpersonatePrivilege, SeAssignPrimaryTokenPrivilege on caller's token.
  2. Scan processes for a SYSTEM-token holder (priority order: winlogon → services → lsass → wininit → all).
  3. OpenProcessTokenDuplicateTokenEx (primary token, TOKEN_ALL_ACCESS).
  4. 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 (no STARTF_USESTDHANDLES), supports interactive programs.
  • Otherwise → anonymous pipes + background relay threads → suitable for scripting.

Fallback Path: ServiceRunner.RunLocal()

  • 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 for SERVICE_STOPPED (configurable timeout).
  • Reads and prints output.txt.
  • Always SafeDeleteDirectory(tmpDir) in finally.

5. Execution Strategy — Remote (ServiceRunner.RunRemote())

  1. Verify \\machine\admin$ is reachable.
  2. Copy RemoteRun.exe to \\machine\admin$\RemoteRun.exe (overwrites).
  3. Create \\machine\admin$\RemoteRun_<GUID>\ (temp dir visible as %SystemRoot%\RemoteRun_<GUID> on target).
  4. Write config.ini to temp dir.
  5. Service binary path on target: %SystemRoot%\RemoteRun.exe --service --workdir "%SystemRoot%\RemoteRun_<GUID>".
  6. OpenSCManager(machine, ...)CreateServiceStartService → wait → collect output → stop/delete.
  7. Cleanup: SafeDeleteDirectory(remoteDir) + File.Delete(remoteExe) — both best-effort.

6. IPC Mechanism (File-based, Temp Directory)

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.


7. Service Lifecycle & Polling

  • State polling: 250 ms intervals using QueryServiceStatus.
  • Max wait for SERVICE_RUNNING: hardcoded 10 s.
  • Max wait for SERVICE_STOPPED: from Options.TimeoutSeconds (default 60 s; 0 = INFINITE).
  • Overflow guard: min(TimeoutSeconds, int.MaxValue/1000) before * 1000 → prevents uint overflow.

8. Win32 API Surface (NativeApi.cs)

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.


9. CLI Interface

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)

10. Build & Packaging

Build

dotnet build RemoteRun\RemoteRun.csproj -c Release

Publish (self-contained single file)

dotnet 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\RemoteRun

Installer

ISCC.exe /DMyAppArch=x64 setup.iss   # → Output\RemoteRun_Setup_x64.exe
ISCC.exe /DMyAppArch=x86 setup.iss   # → Output\RemoteRun_Setup_x86.exe

Installer installs:

  • All published files → {autopf}\RemoteRun\
  • README.md, LICENSE
  • Start Menu: RemoteRun + System CMD shortcuts
  • Optional desktop shortcut (unchecked by default)
  • Requires admin (PrivilegesRequired=admin)
  • Post-install optional: "Open a SYSTEM command prompt now"

11. Known Constraints & Edge Cases

# Observation
1 No stdin piping to childhStdInput = 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

12. Security Posture

  • 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 finally block (local); remote cleanup is best-effort.
  • No secrets stored on disk; no credential handling.
  • Service binary path uses quoted paths for spaces.