diff --git a/README.md b/README.md
index bebdbc09..f97a6b09 100644
--- a/README.md
+++ b/README.md
@@ -6,8 +6,8 @@
[![Chocolatey Downloads](https://img.shields.io/chocolatey/dt/gsudo?label=Chocolatey%20Downloads)](https://community.chocolatey.org/packages/gsudo)
[![GitHub Downloads](https://img.shields.io/github/downloads/gerardog/gsudo/total?label=GitHub%20Downloads)](https://github.com/gerardog/gsudo/releases/latest)
-**gsudo** is a `sudo` equivalent for Windows, with a similar user-experience as the original *nix sudo.
-It allows to run commands with elevated permissions, or to elevate the current shell, in the current console window or a new one.
+**gsudo** is a `sudo` equivalent for Windows, with a similar user-experience as the original Unix/Linux sudo.
+Allows to run commands with elevated permissions, or to elevate the current shell, in the current console window or a new one.
Just prepend `gsudo` (or the `sudo` alias) to your command and it will run elevated. One UAC popup will appear each time. You can see less popups if you enable [gsudo cache](#credentials-cache).
@@ -15,18 +15,45 @@ Just prepend `gsudo` (or the `sudo` alias) to your command and it will run eleva
`gsudo` is very easy to install and use. Its similarities with Unix/Linux sudo make the experience a breeze. It detects your current shell and elevates accordingly (as native shell commands). (Supports `Cmd`, `PowerShell`, `git-bash`, `MinGW`, `Cygwin`, `Yori`, `Take Command`)
+## Table of contents
+
+- [gsudo - a sudo for Windows](#gsudo---a-sudo-for-windows)
+ - [Table of contents](#table-of-contents)
+ - [Documentation](#documentation)
+ - [Demo](#demo)
+ - [Please support gsudo! 💵](#please-support-gsudo-)
+ - [Features](#features)
+ - [Installation](#installation)
+ - [Usage](#usage)
+ - [Config](#config)
+ - [Usage from PowerShell / PowerShell Core](#usage-from-powershell--powershell-core)
+ - [gsudo PowerShell Module](#gsudo-powershell-module)
+ - [PowerShell Alias](#powershell-alias)
+ - [Usage from WSL (Windows Subsystem for Linux)](#usage-from-wsl-windows-subsystem-for-linux)
+ - [Credentials Cache](#credentials-cache)
+ - [Known issues](#known-issues)
+ - [FAQ](#faq)
+
---
+
## Documentation
-**NEW!** Extended documentation available at: https://gerardog.github.io/gsudo/
+**NEW!** Extended documentation available at:
+
+## Demo
+
+![gsudo demo](demo.gif)
+(with `gsudo config CacheMode auto`)
---
+
## Please support gsudo! 💵
- Please consider [sponsoring gsudo](https://gerardog.github.io/gsudo/sponsor). It helps to cover the yearly renewal of the code-signing certificate.
- No money? No problem! Please give us a star! ⭐
---
+
## Features
- Elevated commands are shown in the current user-level console. No new window. (Unless you specify `-n` which opens a new window.)
@@ -34,7 +61,7 @@ Just prepend `gsudo` (or the `sudo` alias) to your command and it will run eleva
- Supports CMD commands: `gsudo md folder` (no need to use the longer form `gsudo cmd.exe /c md folder`)
- Elevates [PowerShell/PowerShell Core commands](#usage-from-powershell--powershell-core), [WSL commands](#usage-from-wsl-windows-subsystem-for-linux), Bash for Windows (Git-Bash/MinGW/MSYS2/Cygwin), Yori or Take Command shell commands.
- Supports being used on scripts:
- - Outputs of the elevated commands can be interpreted: E.g. StdOut/StdErr can be piped or captured (e.g. `gsudo dir | findstr /c:"bytes free" > FreeSpace.txt`) and exit codes too (`%errorlevel%`). If `gsudo` fails to elevate, the exit code will be 999.
+ - Outputs StdOut/StdErr can be piped or captured (e.g. `gsudo dir | findstr /c:"bytes free" > FreeSpace.txt`) and exit codes too (`%errorlevel%`). If `gsudo` fails to elevate, the exit code will be 999.
- If `gsudo` is invoked from an already elevated console, it will just run the command (it won't fail). So, you don't have to worry if you run `gsudo` or a script that uses `gsudo` from an already elevated console. (The UAC popup will not appear, as no elevation is required)
- `gsudo !!` elevates the last executed command. Works on CMD, Git-Bash, MinGW, Cygwin (and PowerShell with [gsudo module](#gsudomodule) only)
@@ -42,42 +69,59 @@ Just prepend `gsudo` (or the `sudo` alias) to your command and it will run eleva
## Installation
- Using [Scoop](https://scoop.sh): `scoop install gsudo`
-- Or using [WinGet](https://github.com/microsoft/winget-cli/releases) `winget install gerardog.gsudo`
-- Or using [Chocolatey](https://chocolatey.org/install): `choco install gsudo`
-- Or manually: Unzip the latest release, and add to the path.
-- Or running:
+- Using [WinGet](https://github.com/microsoft/winget-cli/releases) `winget install gerardog.gsudo`
+- Using [Chocolatey](https://chocolatey.org/install): `choco install gsudo`
+- Or manually: Unzip the latest release, and add to the path.
+- Or running:
+
``` PowerShell
PowerShell -Command "Set-ExecutionPolicy RemoteSigned -scope Process; [Net.ServicePointManager]::SecurityProtocol = 'Tls12'; iwr -useb https://raw.githubusercontent.com/gerardog/gsudo/master/installgsudo.ps1 | iex"
```
-
+
Note: gsudo is portable. No windows service is required or system change is done, except adding gsudo to the Path.
## Usage
-```gsudo``` Opens an elevated shell in the current console.
-
-```gsudo [options] {command} [arguments]```
-Executes the specified command with elevated permissions.
-
-Most relevant **`[options]`**:
+``` powershell
+gsudo [options] # Elevates your current shell
+gsudo [options] {command} [args] # Runs {command} with elevated permissions
+gsudo cache [on | off | help] # Starts/Stops an elevated cache session. (reduced UAC popups)
+gsudo status # Shows current user, cache and console status.
+gsudo !! # Re-run last command as admin. (YMMV)
+```
-- **`-n | --new`** Starts the command in a **new** console with elevated rights (and returns immediately).
-- **`-w | --wait`** Force wait for the process to end (and return the exitcode).
-- **`-s | --system`** Run As Local System account ("NT AUTHORITY\SYSTEM").
-- **`-i | --integrity {v}`** Run command with a specific integrity level: `Low`, `Medium`, `MediumPlus`, `High` (default), `System`. For example, use `Low` to launch a restricted process, or use `Medium` to run without Admin rights.
-- **`-d | --direct`** Execute {command} directly. Does not wrap it with your current shell (Pwsh/WSL/MinGw/Yori/etc). Assumes it is a `CMD` command (eg. an `.EXE` file).
-- **`--loadProfile`** When elevating PowerShell commands, do load profiles.
-- **`--copyns`** Reconnect current connected network shares on the elevated session. Warning! This is verbose, affects the elevated user system-wide (other processes), and can prompt for credentials interactively.
-- **`--debug`** Debug mode (verbose).
+``` powershell
+General options:
+ -n | --new # Starts the command in a new console (and returns immediately).
+ -w | --wait # When in new console, force wait for the command to end.
+
+Security options:
+ -i | --integrity {v} # Specify integrity level: Untrusted, Low, Medium, MediumPlus, High (default), System
+ -u | --user {usr} # Run as the specified user. Asks for password. For local admins shows UAC unless '-i Medium'
+ -s | --system # Run as Local System account (NT AUTHORITY\SYSTEM).
+ --ti # Run as member of NT SERVICE\TrustedInstaller
+ -k # Kills all cached credentials. The next time gsudo is run a UAC popup will be appear.
+
+Shell related options:
+ -d | --direct # Execute {command} directly. Bypass shell wrapper (Pwsh/Yori/etc).
+ --loadProfile # When elevating PowerShell commands, load user profile.
+
+Other options:
+ --loglevel {val} # Set minimum log level to display: All, Debug, Info, Warning, Error, None
+ --debug # Enable debug mode.
+ --copyns # Connect network drives to the elevated user. Warning: Verbose, interactive asks for credentials
+ --copyev # (deprecated) Copy environment variables to the elevated process. (not needed on default console mode)
-```gsudo config```
-Show current user-settings.
+```
-```gsudo config {key} ["value" | --reset]```
-Read, write, or reset a user setting to the default value.
+## Config
-```gsudo status```
-Show status information about current user, security, integrity level or other gsudo relevant data.
+``` powershell
+ gsudo config # Show current config settings & values.
+ gsudo config {key} [--global] [value] # Read or write a user setting
+ gsudo config {key} [--global] --reset # Reset config to default value
+ --global # Affects all users (overrides user settings)
+```
**Note:** You can use anywhere **the `sudo` alias** created by the installers.
@@ -85,11 +129,8 @@ Show status information about current user, security, integrity level or other g
``` powershell
gsudo # elevates the current shell in the current console window (Supports Cmd/PowerShell/Pwsh Core/Yori/Take Command/git-bash/cygwin)
-
gsudo -n # launch the current shell elevated in a new console window
-
gsudo -n -w powershell ./Do-Something.ps1 # launch in new window and wait for exit
-
gsudo notepad %windir%\system32\drivers\etc\hosts # launch windows app
sudo notepad # sudo alias built-in
@@ -103,71 +144,73 @@ gsudo config Prompt --reset # Reset to default value
# Enable credentials cache (less UAC popups):
gsudo config CacheMode Auto
-
-# Elevate last command (sudo bang bang)
-gsudo !!
```
-## Usage from PowerShell / PowerShell Core
+### Usage from PowerShell / PowerShell Core
`gsudo` detects if invoked from PowerShell and elevates PS commands (unless `-d` is used to elevate CMD commands).
-- NEW! (starting `v1.6.0`): Use `gsudo { ScriptBlock }` syntax to elevate PowerShell commands.
-
- The ScriptBlock will ran elevated in a different process and lexical scope, so it can't access your existing `$variables`.
+The command to elevate will ran in a different process, so it **can't access the parent `$variables` and scope.**
- To parametrize the script, you can pass values with `-args` parameter and access them via `$args` array (or try `Invoke-gsudo` function).
+There are 3 possible syntaxes to elevate commands.
+1. Wrap command in {curly braces}. (recommended)
- ``` powershell
- gsudo { Write-Output "Hello World" }
- gsudo { Write-Output $args[0] $args[1] } -args "Hello", "World"
-
- # Output can be captured as serialized PSObjects with properties.
- $services = gsudo { Get-Service 'WSearch', 'Winmgmt'}
- Write-Output $services.DisplayName
+ ``` powershell
+ gsudo { Write-Output "Hello World" }
- # Variable substitution example:
- $file='C:\My Secret.txt'; $algorithm='md5';
- $hash = gsudo {(Get-FileHash args[0] -Algorithm args[1]).Hash} -args $file, $algorithm
- ```
+ # Pass arguments with -args
+ $MyString = "Hello World"
+ gsudo { Write-Output $args[0] } -args $MyString
-- Use **`Invoke-gsudo` wrapper function** to elevate a ScriptBlock.
+ # Output is serialized as PSObjects with properties.
+ $services = gsudo { Get-Service 'WSearch', 'Winmgmt'}
+ Write-Output $services.DisplayName
- To parametrize the script, you can use `$using:variableName` syntax and it´s serialized value will be applied. The result object is serialized and returned (as an object).
+ # Inputs too: Example elevated iteration of a list.
+ Get-ChildItem . | gsudo { $Input.CreationTime}
- ``` PowerShell
- # Accepts pipeline input.
- Get-process SpoolSv | Invoke-gsudo { Stop-Process -Force }
+ # Syntax:
+ gsudo [-nwskd] [-u|--user {username}] [--integrity {i}] [--ti]
+ [--loadProfile] { ScriptBlock }
+ [-args $argument1[..., $argumentN]] ;
+ ```
- # Variable substitution usage:
- $folder = "C:\ProtectedFolder"
- Invoke-gsudo { Remove-Item $using:folder }
+2. **Invoke-gsudo** wrapper function:
- # The result is serialized (PSObject) with properties.
- (Invoke-gsudo { Get-ChildItem $using:folder }).LastWriteTime
- ```
+ ``` powershell
+ $MyString = "Hello World"
+ Invoke-Gsudo { Write-Output $using:MyString }
-- Legacy Syntax (not recommended, quote-escaping hell).
-
- Prepend `gsudo` for commands without special operators `()|&<>` or single quotes `'`. Otherwise you can **pass a string literal** with the command to be elevate:
+ # Syntax:
+ Invoke-Gsudo [-ScriptBlock]
+ [[-ArgumentList] ]
+ [-InputObject ]
+ [-LoadProfile | -NoProfile]
+ [-Credential ]
+ ```
- ``` powershell
- # Elevate Commands without ()|&<>' by prepending gsudo
- gsudo Remove-Item ProtectedFile.txt
-
- # Or pass a string literal:
- gsudo 'Remove-Item ProtectedFile.txt'
- $hash = gsudo '(Get-FileHash "C:\My Secret.txt").Hash'
-
- # Legacy: Variable substitutions example:
- $file='C:\My Secret.txt'; $algorithm='md5';
- $hash = gsudo "(Get-FileHash '$file' -Algorithm $algorithm).Hash"
- # or
- $hash = gsudo "(Get-FileHash ""$file"" -Algorithm $algorithm).Hash"
- ```
+- It is an aproximation of what a native PS-Sudo would be like. Auto serialization of inputs & outputs.
+- You can prefix variables with the `Using` scope modifier (like `$using:variableName`) and their serialized value is applied.
+- Use `-LoadProfile` or `-NoProfile` to override profile loading or not.
+- Use `-Credential` option for Run As User (same as `-u` but for `Get-Credentials`).
+- Better forwarding of your current context to the elevated instance (current Location, $ErrorActionPreference )
+
+3. Manual string interpolation => Not recommended.
+
+ ``` PowerShell
+ # Legacy: Variable substitutions example:
+ $file='C:\My Secret.txt'; $algorithm='md5';
+ $hash = gsudo "(Get-FileHash '$file' -Algorithm $algorithm).Hash"
+ # or
+ $hash = gsudo "(Get-FileHash ""$file"" -Algorithm $algorithm).Hash"
+ ```
-- For a enhanced experience: Import module `gsudoModule.psd1` into your Profile: (also enables `gsudo !!` on PS)
+#### gsudo PowerShell Module
+
+- Import module `gsudoModule.psd1` into your Profile:
+ - Enables `gsudo !!` for PS
+ - Enhanced experience
``` Powershell
# Add the following line to your $PROFILE (replace with full path)
@@ -177,16 +220,20 @@ gsudo !!
Get-Command gsudoModule.psd1 | % { Write-Output "`nImport-Module `"$($_.Source)`"" | Add-Content $PROFILE }
```
+#### PowerShell Alias
+
- You can create a custom alias for gsudo or Invoke-gsudo, as you prefer: (add one of these lines to your $PROFILE)
- - `Set-Alias 'sudo' 'gsudo'` or
+ - `Set-Alias 'sudo' 'gsudo'` or
- `Set-Alias 'sudo' 'Invoke-gsudo'`
+
+---
-## Usage from WSL (Windows Subsystem for Linux)
+### Usage from WSL (Windows Subsystem for Linux)
On WSL, elevation and `root` are different concepts. `root` allows full administration of WSL but not the windows system. Use WSL's native `su` or `sudo` to gain `root` access. To get admin privilege on the Windows box you need to elevate the WSL.EXE process. `gsudo` allows that (a UAC popup will appear).
-On WSL bash, prepend `gsudo` to elevate **WSL commands** or `gsudo -d` for **CMD commands**.
+On WSL bash, prepend `gsudo` to elevate **WSL commands** or `gsudo -d` for **CMD commands**.
``` bash
# elevate default shell
@@ -198,7 +245,6 @@ PC:~$ gsudo mkdir /mnt/c/Windows/MyFolder
# run elevated Windows command
PC:~$ gsudo -d notepad C:/Windows/System32/drivers/etc/hosts
PC:~$ gsudo -d "notepad C:\Windows\System32\drivers\etc\hosts"
-PC:~$ gsudo -d "echo 127.0.0.1 www.MyWeb.com >> %windir%\System32\drivers\etc\hosts"
# test for gsudo and command success
retval=$?;
@@ -213,14 +259,21 @@ fi;
## Credentials Cache
-The `Credentials Cache` allows to elevate several times from a parent process with only one UAC pop-up.
+The `Credentials Cache`, if enabled and active, allows to elevate several times from a parent process with only one UAC pop-up.
-[Learn more](https://gerardog.github.io/gsudo/docs/credentials-cache)
+It is convenient, but it's safe only if you are not already hosting a malicious process: No matter how secure gsudo itself is, a malicious process could trick the allowed process (e.g. Cmd/Powershell) and force a running gsudo cache instance to elevate silently.
-## Demo
+How to use, very briefly:
-(with `gsudo config CacheMode auto`)
-![gsudo demo](demo.gif)
+- Manually start/stop a cache session with `gsudo cache {on | off}`.
+- Stop all cache sessions with `gsudo -k`.
+- Available Cache Modes:
+ * `Disabled:` Every elevation shows a UAC popup.
+ * `Explicit:` (default) Every elevation shows a UAC popup, unless a cache session is started with `gsudo cache on`
+ * `Auto:` Simil-unix-sudo. The first elevation shows a UAC Popup and starts a cache session automatically.
+- Change Cache mode with `gsudo config CacheMode Disabled|Explicit|Auto`
+
+[Learn more](https://gerardog.github.io/gsudo/docs/credentials-cache)
## Known issues
@@ -238,7 +291,7 @@ The `Credentials Cache` allows to elevate several times from a parent process wi
- Why did you migrated from `.Net Framework 4.6` to `.Net Core 7.0`?
- Starting from v1.4.0, it is built using `.Net 7.0` NativeAOT. It loads faster and uses less memory, and runs on machines without any .Net runtime installed. Prior versions ` FreeSpace.txt
-# Elevate last command (sudo bang bang)
-gsudo !!
+gsudo config LogLevel "Error" # Configure Reduced logging
+gsudo config Prompt "$P [elevated]$G " # Configure a custom Elevated Prompt
+gsudo config Prompt --reset # Reset to default value
+
+# Enable credentials cache (less UAC popups):
+gsudo config CacheMode Auto
```
### Configuration
-``` powershell
-# See current configuration
-gsudo config
-# Configure Reduced logging
-gsudo config LogLevel "Error"
-# Configure a custom Elevated Prompt
-gsudo config Prompt "$P [elevated]$G "
-# Reset to default value
-gsudo config Prompt --reset
-# Enable credentials cache (less UAC popups):
-gsudo config CacheMode Auto
+``` powershell
+ gsudo config # Show current config settings & values.
+ gsudo config {key} [--global] [value] # Read or write a user setting
+ gsudo config {key} [--global] --reset # Reset config to default value
+ --global # Affects all users (overrides user settings)
```
\ No newline at end of file
diff --git a/src/gsudo.Tests/CmdTests.cs b/src/gsudo.Tests/CmdTests.cs
index e1f85167..c0a07865 100644
--- a/src/gsudo.Tests/CmdTests.cs
+++ b/src/gsudo.Tests/CmdTests.cs
@@ -24,7 +24,7 @@ public void Cmd_DebugTestHelper()
//[TestMethod]
public void Cmd_AdminUserTest()
{
- Assert.IsTrue(ProcessHelper.IsAdministrator(), "This test suite is intended to be run as an administrator, otherwise it will not work.");
+ Assert.IsTrue(SecurityHelper.IsAdministrator(), "This test suite is intended to be run as an administrator, otherwise it will not work.");
}
[TestMethod]
@@ -42,10 +42,10 @@ public void Cmd_ChangeDirTest()
// TODO: Test --raw, --vt, --attached
var testDir = Environment.CurrentDirectory;
var p1 = new TestProcess(
- $"\"{testDir}\\gsudo\" cmd /c cd \r\n"
+ $"\"{testDir}\\gsudo\" cmd /c cd \r\n" // => show current path
+ $"cd .. \r\n"
+ $"\"{testDir}\\gsudo\" cmd /c cd \r\n"
- );
+ ); ;
p1.WaitForExit();
var otherDir = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory,".."));
@@ -102,12 +102,12 @@ public void Cmd_CommandLineAppNoWaitTest()
public void Cmd_WindowsAppWaitTest()
{
bool stillWaiting = false;
- var p = new TestProcess("gsudo \"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
+ var p = new TestProcess("gsudo -w \"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
try
{
p.WaitForExit(3000);
}
- finally
+ catch
{
stillWaiting = true;
}
@@ -120,16 +120,17 @@ public void Cmd_WindowsAppWaitTest()
[TestMethod]
public void Cmd_WindowsAppNoWaitTest()
{
- var p = new TestProcess("gsudo notepad");
+ var p = new TestProcess("gsudo \"c:\\Program Files (x86)\\Windows NT\\Accessories\\wordpad.exe\"");
try
{
p.WaitForExit();
}
finally
{
- Process.Start("gsudo", "taskkill.exe /FI \"WINDOWTITLE eq Untitled - Notepad\" ").WaitForExit();
- p.WaitForExit();
+ Process.Start("gsudo", "taskkill.exe /IM Wordpad.exe").WaitForExit();
}
+
+ p.WaitForExit();
}
[TestMethod]
diff --git a/src/gsudo.Tests/PowershellTests.cs b/src/gsudo.Tests/PowershellTests.cs
index 0bd82c6b..d0c7066a 100644
--- a/src/gsudo.Tests/PowershellTests.cs
+++ b/src/gsudo.Tests/PowershellTests.cs
@@ -1,4 +1,5 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using gsudo.Commands;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace gsudo.Tests
@@ -11,7 +12,24 @@ public class PowerShellCoreTests : PowerShellTests
public PowerShellCoreTests()
{
- PS_FILENAME = "pwsh.exe";
+ PS_FILENAME = "pwsh.exe";
+ }
+ }
+
+ [TestClass]
+ public class PowerShellCoreAttachedTests : PowerShellTests
+ {
+ [ClassInitialize]
+ public static new void ClassInitialize(TestContext context)
+ {
+ TestShared.StartCacheSession();
+ new ConfigCommand() { key = "ForceAttachedConsole", value = new string[] { "true" } }.Execute();
+ }
+
+ [ClassCleanup]
+ public static new void ClassCleanup()
+ {
+ new ConfigCommand() { key = "ForceAttachedConsole", value = new string[] { "--reset" } }.Execute();
}
}
diff --git a/src/gsudo.Tests/TestProcess.cs b/src/gsudo.Tests/TestProcess.cs
index 6e9d0917..223863b9 100644
--- a/src/gsudo.Tests/TestProcess.cs
+++ b/src/gsudo.Tests/TestProcess.cs
@@ -13,7 +13,6 @@ class TestProcess
public uint ProcessId { get; set; }
public int ExitCode;
- static int TestNumber = 1;
private readonly string _testId = Random.Shared.Next(1,999999).ToString() ;// DateTime.Now.ToString("yyyyMMddHHmmssff");
string _sIn => $"in{_testId}";
string _sOut => $"out{_testId}";
@@ -41,7 +40,7 @@ public TestProcess(string inputScript, string shell = "cmd /k")
File.WriteAllText($"{_sIn}", inputScript + "\r\nExit /b %errorlevel%\r\n");
- _process = ProcessFactory.StartDetached(_batchFile, arguments, Environment.CurrentDirectory, false);
+ _process = ProcessFactory.StartDetached(_batchFile, arguments, Environment.CurrentDirectory, false);
_testProcessHandle = new SafeProcessHandle(_process.Handle, false);
ProcessId = (uint) _process.Id;
diff --git a/src/gsudo.Wrappers/Invoke-gsudo.ps1 b/src/gsudo.Wrappers/Invoke-gsudo.ps1
index 7eff9a5e..7fbac2e4 100644
--- a/src/gsudo.Wrappers/Invoke-gsudo.ps1
+++ b/src/gsudo.Wrappers/Invoke-gsudo.ps1
@@ -7,24 +7,28 @@ Serializes a scriptblock and executes it in an elevated powershell.
The ScriptBlock runs in a different process, so it can´t read/write variables from the invoking scope.
If you reference a variable in a scriptblock using the `$using:variableName` it will be replaced with it´s serialized value.
The elevated command can accept input from the pipeline with $Input. It will be serialized, so size matters.
-The script result is serialized, sent back to the non-elevated instance, and returned.
-Optionally you can check for "$LastExitCode -eq 999" to find out if gsudo failed to elevate (UAC popup cancelled)
+The command result is serialized, sent back to the non-elevated instance, deserealized and returned.
+
+Optionally you can check for "$LastExitCode -eq 999" to find out if gsudo failed to elevate (for example, UAC popup cancelled)
.PARAMETER ScriptBlock
Specifies a ScriptBlock that will be run in an elevated PowerShell instance. '
e.g. { Get-Process Notepad }
.PARAMETER ArgumentList
-An list of elements that will be accesible inside the script as: $args
+An list of elements that will be accesible inside the script as: $args[0] ... $args[n]
+
+.PARAMETER LoadProfile
+Load the user profile in the elevated powershell instance. (regardless of `gsudo config PowerShellLoadProfile`)
-.PARAMETER NoElevate
-A test mode where the command is executed out-of-scope but without real elevation: The serialization/marshalling is still done.
+.PARAMETER NoProfile
+Do not load the user profile in the elevated powershell instance. (regardless of `gsudo config PowerShellLoadProfile`)
.INPUTS
You can pipe any object to Invoke-Gsudo. It will be serialized and available in the userScript as $Input.
.OUTPUTS
-Whatever the scriptblock returns. Use explicit "return" in your scriptblock.
+Whatever the scriptblock returns.
.EXAMPLE
PS> Get-Process notepad | Invoke-gsudo { Stop-Process }
@@ -63,10 +67,9 @@ param
[switch]
$NoProfile = $false,
- #test mode
[Parameter()]
- [switch]
- $NoElevate = $false
+ [System.Management.Automation.PSCredential]
+ $Credential
)
# Replaces $using:variableName with the serialized value of $variableName.
@@ -137,57 +140,66 @@ if ($Debug) {
Write-Debug "Full Script to run on the isolated instance: { $remoteCmd }"
}
-if($NoElevate) {
- # We could invoke using Invoke-Command:
- # $result = $InputObject | Invoke-Command (Deserialize-Scriptblock $remoteCmd) -ArgumentList $ArgumentList
- # Or run in a Job to ensure same variable isolation:
+$pwsh = ("""$([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)""") # Get same running powershell EXE.
+
+if ($host.Name -notmatch 'consolehost') { # Workaround for PowerShell ISE, or PS hosted inside other process
+ if ($PSVersionTable.PSVersion.Major -le 5)
+ { $pwsh = "powershell.exe" }
+ else
+ { $pwsh = "pwsh.exe" }
+}
+
+$windowTitle = $host.ui.RawUI.WindowTitle;
+
+$dbg = if ($debug) {"--debug "} else {" "}
- $job = Start-Job -ScriptBlock (Deserialize-Scriptblock $remoteCmd) -errorAction $errorAction | Wait-Job;
- $result = Receive-Job $job -errorAction $errorAction
+if ($LoadProfile -and (-not $NoProfile -or (gsudo.exe --loglevel None config PowerShellLoadProfile).Split(" = ")[1] -like "*true*")) {
+ $sNoProfile = ""
} else {
- $pwsh = ("""$([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)""") # Get same running powershell EXE.
-
- if ($host.Name -notmatch 'consolehost') { # Workaround for PowerShell ISE, or PS hosted inside other process
- if ($PSVersionTable.PSVersion.Major -le 5)
- { $pwsh = "powershell.exe" }
- else
- { $pwsh = "pwsh.exe" }
- }
-
- $windowTitle = $host.ui.RawUI.WindowTitle;
+ $sNoProfile = "-NoProfile "
+}
- $dbg = if ($debug) {"--debug "} else {" "}
+if ($credential) {
+ $currentSid = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value;
+ $user = "-u $($credential.UserName) "
- if ($LoadProfile -or ((gsudo.exe --loglevel None config Powershellloadprofile).Split(" = ")[1] -like "*true*" -and -not $NoProfile)) {
- $sNoProfile = ""
- } else {
- $sNoProfile = "-NoProfile "
- }
+ # At the time of writing this, there is no way (considered secure) to send the password to gsudo. So instead of sending the password, lets start a credentials cache instance.
+ Start-Process "gsudo.exe" -Args "$dbg -u $($credential.UserName) gsudoservice $PID $CurrentSid All 00:05:00" -credential $Credential -LoadUserProfile -WorkingDirectory "$env:windir" *> $null
+ # This may fail with `The specified drive root "C:\Users\gerar\AppData\Local\Temp\" either does not exist, or it is not a folder.` https://github.com/PowerShell/PowerShell/issues/18333
- $arguments = "-d --LogLevel Error $dbg$pwsh -nologo $sNoProfile-NonInteractive -OutputFormat Xml -InputFormat Text -encodedCommand IAAoACQAaQBuAHAAdQB0ACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAKQAgAHwAIABpAGUAeAAgAA==".Split(" ")
-
- # Must Read: https://stackoverflow.com/questions/68136128/how-do-i-call-the-powershell-cli-robustly-with-respect-to-character-encoding-i?noredirect=1&lq=1
- $result = $remoteCmd | & gsudo.exe $arguments *>&1
-
- $host.ui.RawUI.WindowTitle = $windowTitle;
+ #$p.WaitForExit();
+ Start-Sleep -Seconds 1
+} else {
+ $user = "";
}
-ForEach ($item in $result)
-{
- if (
- $item.psobject.Properties['Exception'] -and
- ($item.Exception.SerializedRemoteException.WasThrownFromThrowStatement -or
- $item.Exception.WasThrownFromThrowStatement)
- )
+$arguments = "-d --LogLevel Error $user$dbg$pwsh $sNoProfile -nologo -NonInteractive -OutputFormat Xml -InputFormat Text -encodedCommand IAAoACQAaQBuAHAAdQB0ACAAfAAgAE8AdQB0AC0AUwB0AHIAaQBuAGcAKQAgAHwAIABpAGUAeAAgAA==".Split(" ")
+# Must Read: https://stackoverflow.com/questions/68136128/how-do-i-call-the-powershell-cli-robustly-with-respect-to-character-encoding-i?noredirect=1&lq=1
+
+$result = $remoteCmd | & gsudo.exe $arguments *>&1
+
+$host.ui.RawUI.WindowTitle = $windowTitle;
+
+& {
+ Set-StrictMode -Off #within this scope
+
+ ForEach ($item in $result)
{
- throw $item
- }
- if ($item -is [System.Management.Automation.ErrorRecord])
- {
- Write-Error $item
- }
- else
- {
- Write-Output $item;
+ if (
+ $item.psobject.Properties['Exception'] -and
+ ($item.Exception.SerializedRemoteException.WasThrownFromThrowStatement -or
+ $item.Exception.WasThrownFromThrowStatement)
+ )
+ {
+ throw $item
+ }
+ if ($item -is [System.Management.Automation.ErrorRecord])
+ {
+ Write-Error $item
+ }
+ else
+ {
+ Write-Output $item;
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/gsudo.Wrappers/gsudoModule.psd1 b/src/gsudo.Wrappers/gsudoModule.psd1
index 8352e578..3ecaacac 100644
--- a/src/gsudo.Wrappers/gsudoModule.psd1
+++ b/src/gsudo.Wrappers/gsudoModule.psd1
@@ -69,7 +69,7 @@ Copyright = '(c) Gerardo Grignoli. All rights reserved.'
# NestedModules = @()
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
-FunctionsToExport = 'gsudo', 'invoke-gsudo'
+FunctionsToExport = 'gsudo', 'invoke-gsudo', 'split-gsudo'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
diff --git a/src/gsudo.Wrappers/gsudoModule.psm1 b/src/gsudo.Wrappers/gsudoModule.psm1
index 678736a6..3f4016cc 100644
--- a/src/gsudo.Wrappers/gsudoModule.psm1
+++ b/src/gsudo.Wrappers/gsudoModule.psm1
@@ -30,6 +30,9 @@ function gsudo {
}
}
+function Split-Gsudo { & wt -w 0 sp gsudo pwsh -c ";" @args }
+
$gsudoVerbose=$true;
-Export-ModuleMember -function Invoke-Gsudo, gsudo -Variable gsudoVerbose
+Export-ModuleMember -function Invoke-Gsudo, gsudo, Split-Gsudo -Variable gsudoVerbose
+
diff --git a/src/gsudo/Commands/AttachRunCommand.cs b/src/gsudo/Commands/AttachRunCommand.cs
new file mode 100644
index 00000000..cc8a5184
--- /dev/null
+++ b/src/gsudo/Commands/AttachRunCommand.cs
@@ -0,0 +1,60 @@
+using System;
+using gsudo.Helpers;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using gsudo.Native;
+
+namespace gsudo.Commands
+{
+ ///
+ /// This command attaches to the parent console, then executes the command.
+ /// This works even if the parent has higher integrity level than us.
+ /// This must be launched by the caller gsudo and not the elevated service, because the parent process id must have the user console.
+ ///
+ class AttachRunCommand : ICommand
+ {
+ public IEnumerable CommandToRun { get; private set; }
+
+ public AttachRunCommand(IEnumerable commandToRun)
+ {
+ CommandToRun = commandToRun;
+ }
+
+ public Task Execute()
+ {
+ ConsoleApi.FreeConsole();
+ if (!ConsoleApi.AttachConsole(-1))
+ {
+ ConsoleApi.AllocConsole();
+ throw new ApplicationException($"Failed to attach console: {new Win32Exception()}");
+ }
+
+ var app = CommandToRun.First();
+ var args = string.Join(" ", CommandToRun.Skip(1).ToArray());
+
+ if (InputArguments.IntegrityLevel.HasValue &&
+ (int) InputArguments.IntegrityLevel != SecurityHelper.GetCurrentIntegrityLevel() &&
+ Environment.GetEnvironmentVariable("gsudoAttachRun") != "1")
+ {
+ Environment.SetEnvironmentVariable("gsudoAttachRun", "1"); // prevents infinite loop on machines with UAC disabled.
+
+ var process = ProcessFactory.StartAttachedWithIntegrity(
+ InputArguments.GetIntegrityLevel(), app, args, Directory.GetCurrentDirectory(), false, true);
+
+ process.GetProcessWaitHandle().WaitOne();
+
+ if (ProcessApi.GetExitCodeProcess(process, out var exitCode))
+ return Task.FromResult(exitCode);
+ }
+ else
+ {
+ ProcessFactory.StartAttached(app, args).WaitForExit();
+ }
+
+ return Task.FromResult(0);
+ }
+ }
+}
diff --git a/src/gsudo/Commands/BangBangCommand.cs b/src/gsudo/Commands/BangBangCommand.cs
index 6d028961..dd911a1f 100644
--- a/src/gsudo/Commands/BangBangCommand.cs
+++ b/src/gsudo/Commands/BangBangCommand.cs
@@ -1,6 +1,7 @@
using gsudo.Helpers;
using System;
using System.Diagnostics;
+using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@@ -13,6 +14,12 @@ class BangBangCommand : ICommand
public Task Execute()
{
+ if (ShellHelper.InvokingShell.In (Shell.PowerShell, Shell.PowerShellCore, Shell.PowerShellCore623BuggedGlobalInstall))
+ {
+ var module = Path.Combine(Path.GetDirectoryName(ProcessHelper.GetOwnExeName()), "gsudoModule.psd1");
+ throw new ApplicationException($"To use `gsudo !!` from powershell, run or add the following line to your `$PROFILE:\n\n Import-Module '{ module }'");
+ }
+
var caller = Process.GetCurrentProcess().GetShellProcess().MainModule.ModuleName;
var length = (int)NativeMethods.GetConsoleCommandHistoryLength(caller);
@@ -51,9 +58,8 @@ public Task Execute()
Logger.Instance.Log("Command to run: " + commandToElevate, LogLevel.Info);
- return new RunCommand()
- { CommandToRun = ArgumentsHelper.SplitArgs(commandToElevate) }
- .Execute();
+ return new RunCommand(commandToRun: ArgumentsHelper.SplitArgs(commandToElevate))
+ .Execute();
}
class NativeMethods
diff --git a/src/gsudo/Commands/CacheCommand.cs b/src/gsudo/Commands/CacheCommand.cs
index 8fc3defa..df103eeb 100644
--- a/src/gsudo/Commands/CacheCommand.cs
+++ b/src/gsudo/Commands/CacheCommand.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
-using gsudo.Enums;
+using gsudo.CredentialsCache;
using gsudo.Helpers;
using gsudo.Rpc;
@@ -19,6 +19,7 @@ class CacheCommand : ICommand
{
public CacheCommandAction? Action { get; set; }
public int? AllowedPid { get; set; }
+ public string AllowedSid { get; set; }
public TimeSpan? CacheDuration { get; set; }
public async Task Execute()
@@ -56,8 +57,7 @@ public async Task Execute()
return 1;
}
-
- if (!ProcessHelper.IsAdministrator() && NamedPipeClient.IsServiceAvailable())
+ if (!SecurityHelper.IsAdministrator() && NamedPipeClient.IsServiceAvailable(AllowedPid, AllowedSid))
{
var commandToRun = new List();
commandToRun.Add($"\"{ProcessHelper.GetOwnExeName()}\"");
@@ -68,15 +68,13 @@ public async Task Execute()
InputArguments.Wait = true;
InputArguments.Direct = true;
- return await new RunCommand() {CommandToRun = commandToRun, }
+ return await new RunCommand(commandToRun)
.Execute().ConfigureAwait(false);
}
else
{
- if (!ServiceHelper.StartElevatedService(AllowedPid.Value, CacheDuration ?? Settings.CacheDuration))
- {
- return Constants.GSUDO_ERROR_EXITCODE;
- }
+ ServiceHelper.StartService(AllowedPid.Value, CacheDuration ?? Settings.CacheDuration, AllowedSid);
+
if (AllowedPid.Value != 0)
Logger.Instance.Log($"Elevation allowed for process Id {AllowedPid.Value} and children.",
LogLevel.Info);
@@ -87,7 +85,7 @@ public async Task Execute()
LogLevel.Warning);
// wait until the cache service becomes available (2 seconds max)
- for (int i=0; i<40 && !NamedPipeClient.IsServiceAvailable(AllowedPid); i++)
+ for (int i=0; i<40 && !NamedPipeClient.IsServiceAvailable(AllowedPid, AllowedSid); i++)
await Task.Delay(50).ConfigureAwait(false);
}
diff --git a/src/gsudo/Commands/ConfigCommand.cs b/src/gsudo/Commands/ConfigCommand.cs
index 60820888..2e4ee925 100644
--- a/src/gsudo/Commands/ConfigCommand.cs
+++ b/src/gsudo/Commands/ConfigCommand.cs
@@ -16,6 +16,11 @@ public Task Execute()
{
RegistrySetting setting = null;
+ if (key.In("-h", "/h", "/?", "-?", "--help", "help"))
+ {
+ return new HelpCommand().Execute();
+ }
+
if (key == null)
{
// print all configs
@@ -32,10 +37,7 @@ public Task Execute()
Settings.AllKeys.TryGetValue(key, out setting);
if (setting == null)
- {
- Logger.Instance.Log($"Invalid Setting '{key}'.", LogLevel.Error);
- return Task.FromResult(Constants.GSUDO_ERROR_EXITCODE);
- }
+ throw new ApplicationException($"Invalid Setting '{key}'.");
if (value != null && value.Any()) // Write Setting
{
@@ -61,15 +63,13 @@ public Task Execute()
InputArguments.Global = true;
}
- if (InputArguments.Global && !ProcessHelper.IsAdministrator())
+ if (InputArguments.Global && !SecurityHelper.IsAdministrator())
{
Logger.Instance.Log($"Global system settings requires elevation. Elevating...", LogLevel.Info);
InputArguments.Direct = true;
- return new RunCommand()
- {
- CommandToRun = new string[]
+ return new RunCommand(commandToRun: new string[]
{ $"\"{ProcessHelper.GetOwnExeName()}\"", "--global", "config", key, reset ? "--reset" : $"\"{unescapedValue}\""}
- }.Execute();
+ ).Execute();
}
if (reset)
@@ -77,7 +77,7 @@ public Task Execute()
else
setting.Save(unescapedValue, InputArguments.Global);
- if (setting.Name == Settings.CacheMode.Name && unescapedValue.In(Enums.CacheMode.Disabled.ToString()))
+ if (setting.Name == Settings.CacheMode.Name && unescapedValue.In(CredentialsCache.CacheMode.Disabled.ToString()))
new KillCacheCommand().Execute();
if (setting.Name.In (Settings.CacheDuration.Name, Settings.SecurityEnforceUacIsolation.Name))
new KillCacheCommand().Execute();
diff --git a/src/gsudo/Commands/CtrlCCommand.cs b/src/gsudo/Commands/CtrlCCommand.cs
index 14a9c696..718d8ea0 100644
--- a/src/gsudo/Commands/CtrlCCommand.cs
+++ b/src/gsudo/Commands/CtrlCCommand.cs
@@ -6,6 +6,7 @@
namespace gsudo.Commands
{
+ // Required for sending Ctrl-C / Ctrl-Break to the elevated process on VT & piped Mode.
class CtrlCCommand : ICommand
{
public int Pid { get; set; }
diff --git a/src/gsudo/Commands/HelpCommand.cs b/src/gsudo/Commands/HelpCommand.cs
index b5dcbd7a..38227525 100644
--- a/src/gsudo/Commands/HelpCommand.cs
+++ b/src/gsudo/Commands/HelpCommand.cs
@@ -13,54 +13,68 @@ public virtual Task Execute()
return Task.FromResult(0);
}
- internal static void ShowVersion()
+ internal static void ShowVersion(bool verbose = true)
{
var assembly = Assembly.GetExecutingAssembly();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"{assembly.GetName().Name} v{GitVersionInformation.FullSemVer} ({GitVersionInformation.FullBuildMetaData})");
Console.ResetColor();
- Console.WriteLine("Copyright(c) 2019-2022 Gerardo Grignoli and GitHub contributors");
+ if (verbose) Console.WriteLine("Copyright(c) 2019-2022 Gerardo Grignoli and GitHub contributors");
}
internal static void ShowHelp()
{
- ShowVersion();
+ ShowVersion(false);
+
+ /*
+ -h | --help\t\tShows this help
+ -v | --version\t\tShows gsudo version
+ */
+
Console.WriteLine(@"
Usage:
-------
-gsudo [options]\t\t\t\tElevates your current shell
-gsudo [options] {command} [args]\tRuns {command} with elevated permissions
-gsudo [-h | --help]\t\t\tShows this help
-gsudo [-v | --version]\t\t\tShows gsudo version
-gsudo cache [on | off | help] \t\tStarts/Stops an elevated cache session. (reduced UAC popups)
-gsudo config\t\t\t\tShow current config settings & values.
-gsudo config {key} [--global] [value] \tRead or write a user setting
-gsudo config {key} [--global] --reset \tReset config to default value
-gsudo status\t\t\t\tShow status about current user, security, integrity level or other gsudo relevant data.
+ gsudo [options]\t\t\tElevates your current shell
+ gsudo [options] {command} [args]\tRuns {command} with elevated permissions
+ gsudo cache [on | off | help] \t\tStarts/Stops an elevated cache session. (reduced UAC popups)
+ gsudo status\t\t\t\tShows current user, cache and console status.
+ gsudo !!\t\t\t\tRe-run last command as admin. (YMMV)
General options:
-n | --new Starts the command in a new console (and returns immediately).
- -w | --wait When in new console, force wait for the command to end.
+ -w | --wait When in new console, wait for the command to end.
Security options:
- -i | --integrity {v} Specify integrity level: Untrusted, Low, Medium, MediumPlus, High (default), System
- -k | --reset-timestamp Kills all cached credentials. The next time gsudo is run a UAC popup will be appear.
+ -i | --integrity {v} Run with integrity level: Untrusted, Low, Medium, MediumPlus, High (default), System
+ -u | --user {username} Run as the specified user. Asks for password. For local admins shows UAC unless '-i Medium'
-s | --system Run as Local System account (NT AUTHORITY\SYSTEM).
- --ti Run as member of NT SERVICE\TrustedInstaller
+ --ti Run as member of NT SERVICE\TrustedInstaller group
+ -k | --reset-timestamp Kills all cached credentials. The next time gsudo is run a UAC popup will be appear.
Shell related options:
- -d | --direct Execute {command} directly. Bypass shell wrapper (Pwsh/Yori/etc).
- --loadProfile When elevating PowerShell commands, load user profile.
+ -d | --direct Skip Shell detection. Asume CMD shell or CMD {command}.
Other options:
--loglevel {val} Set minimum log level to display: All, Debug, Info, Warning, Error, None
--debug Enable debug mode.
- --copyns Connect network drives to the elevated user. Warning: Verbose, interactive asks for credentials
- --copyev (deprecated) Copy environment variables to the elevated process. (not needed on default console mode)
+ --copyns Connect network drives to the elevated user. Warning: Interactive asks for credentials
+ --copyev (deprecated) Copy all environment variables to the elevated process.
+
+Configuration:
+ gsudo config\t\t\t\tShow current config settings & values.
+ gsudo config {key} [--global] [value] \tRead or write a user setting
+ gsudo config {key} [--global] --reset \tReset config to default value
+ --global\t\t\t\tAffects all users (overrides user settings)
+
+From PowerShell:
+ gsudo [options] [--loadProfile] { ScriptBlock } [-args $argument1 [..., $argumentN]]
+ { ScriptBlock }\tMust be wrapped in { curly brackets }
+ --loadProfile\tWhen elevating PowerShell commands, load user profile.
-Learn more about security considerations of using gsudo at: https://gerardog.github.io/gsudo/docs/security".ReplaceOrdinal("\\t", "\t"));
+Learn more about security considerations of using gsudo at: https://gerardog.github.io/gsudo/docs/security
+"
+.ReplaceOrdinal("\\t", "\t"));
return;
}
}
diff --git a/src/gsudo/Commands/KillCacheCommand.cs b/src/gsudo/Commands/KillCacheCommand.cs
index d3995a27..e87fa40e 100644
--- a/src/gsudo/Commands/KillCacheCommand.cs
+++ b/src/gsudo/Commands/KillCacheCommand.cs
@@ -1,47 +1,47 @@
-using System;
-using System.Threading.Tasks;
-
-namespace gsudo.Commands
-{
- ///
- /// Command that signals 'kill-cache' to all gsudo services.
- ///
- public class KillCacheCommand : ICommand
- {
- public bool Verbose { get; set; }
-
- public KillCacheCommand()
- {
- Verbose = true;
- }
-
- public KillCacheCommand(bool verbose)
- {
- Verbose = verbose;
- }
-
- public Task Execute()
- {
- try
- {
- if (CredentialsCacheLifetimeManager.ClearCredentialsCache())
- {
- if (Verbose)
- Logger.Instance.Log("All credentials cache were invalidated.", LogLevel.Info);
- }
- else
- {
- if (Verbose)
- Logger.Instance.Log("No active credentials found to invalidate.", LogLevel.Info);
- }
-
- return Task.FromResult(0);
- }
- catch (Exception ex)
- {
- Logger.Instance.Log($"Failed to invalidate Credentials Cache: {ex.ToString()}", LogLevel.Error);
- return Task.FromResult(Constants.GSUDO_ERROR_EXITCODE);
- }
- }
- }
-}
+using System;
+using System.Threading.Tasks;
+using gsudo.CredentialsCache;
+
+namespace gsudo.Commands
+{
+ ///
+ /// Command that signals 'kill-cache' to all gsudo services.
+ ///
+ public class KillCacheCommand : ICommand
+ {
+ public bool Verbose { get; set; }
+
+ public KillCacheCommand()
+ {
+ Verbose = true;
+ }
+
+ public KillCacheCommand(bool verbose)
+ {
+ Verbose = verbose;
+ }
+
+ public Task Execute()
+ {
+ try
+ {
+ if (CredentialsCacheLifetimeManager.ClearCredentialsCache())
+ {
+ if (Verbose)
+ Logger.Instance.Log("All credentials cache were invalidated.", LogLevel.Info);
+ }
+ else
+ {
+ if (Verbose)
+ Logger.Instance.Log("No active credentials found to invalidate.", LogLevel.Info);
+ }
+
+ return Task.FromResult(0);
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationException($"Failed to invalidate Credentials Cache: {ex.ToString()}");
+ }
+ }
+ }
+}
diff --git a/src/gsudo/Commands/RunCommand.cs b/src/gsudo/Commands/RunCommand.cs
index f71dcfa3..46e211cf 100644
--- a/src/gsudo/Commands/RunCommand.cs
+++ b/src/gsudo/Commands/RunCommand.cs
@@ -2,38 +2,48 @@
using gsudo.Native;
using gsudo.ProcessRenderers;
using gsudo.Rpc;
+using gsudo.Tokens;
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
using System.Linq;
-using System.Security.AccessControl;
using System.Security.Principal;
-using System.Text;
using System.Threading.Tasks;
-using gsudo.Enums;
namespace gsudo.Commands
{
public class RunCommand : ICommand
- {
- public IList CommandToRun { get; set; }
+ {
+ public IList CommandToRun { get; private set; }
private string GetArguments() => GetArgumentsString(CommandToRun, 1);
+ public RunCommand(IList commandToRun)
+ {
+ CommandToRun = commandToRun;
+ }
+
public async Task Execute()
- {
+ {
+ if (InputArguments.IntegrityLevel == IntegrityLevel.System && !InputArguments.RunAsSystem)
+ {
+ Logger.Instance.Log($"Elevating as System because of IntegrityLevel=System parameter.", LogLevel.Warning);
+ InputArguments.RunAsSystem = true;
+ }
+
bool isRunningAsDesiredUser = IsRunningAsDesiredUser();
bool isElevationRequired = IsElevationRequired();
bool isShellElevation = !CommandToRun.Any(); // are we auto elevating the current shell?
- if (isElevationRequired & ProcessHelper.GetCurrentIntegrityLevel() < (int)IntegrityLevel.Medium)
- throw new ApplicationException("Sorry, gsudo doesn't allow to elevate from low integrity level."); // This message is not a security feature, but a nicer error message. It would have failed anyway since the named pipe's ACL restricts it.
+ if (isElevationRequired & SecurityHelper.GetCurrentIntegrityLevel() < (int)IntegrityLevel.Medium)
+ throw new ApplicationException("Sorry, gsudo doesn't allow to elevate from low integrity level."); // This message is not a security feature, but a nicer error message. It would have failed anyway since the named pipe's ACL restricts it.
+
+ if (isRunningAsDesiredUser && isShellElevation && !InputArguments.NewWindow)
+ throw new ApplicationException("Already running as the specified user/permission-level (and no command specified). Exiting...");
CommandToRun = CommandToRunGenerator.AugmentCommand(CommandToRun.ToArray());
bool isWindowsApp = ProcessFactory.IsWindowsApp(CommandToRun.FirstOrDefault());
- var elevationMode = GetElevationMode(isWindowsApp);
+ var elevationMode = GetElevationMode();
CommandToRun = CommandToRunGenerator.FixCommandExceptions(CommandToRun);
@@ -66,47 +76,44 @@ public async Task Execute()
SetRequestPrompt(elevationRequest);
Logger.Instance.Log($"Command to run: {elevationRequest.FileName} {elevationRequest.Arguments}", LogLevel.Debug);
-
- if (isRunningAsDesiredUser && isShellElevation && !InputArguments.NewWindow)
- {
- Logger.Instance.Log("Already running as the specified user/permission-level (and no command specified). Exiting...", LogLevel.Error);
- return Constants.GSUDO_ERROR_EXITCODE;
- }
-
+
if (isRunningAsDesiredUser || !isElevationRequired) // already elevated or running as correct user. No service needed.
{
return RunWithoutService(exeName, GetArguments(), elevationRequest);
}
- return await RunUsingElevatedService(elevationRequest).ConfigureAwait(false);
+ return await RunUsingService(elevationRequest).ConfigureAwait(false);
}
private static void SetRequestPrompt(ElevationRequest elevationRequest)
{
- if ((int)InputArguments.GetIntegrityLevel() < (int)IntegrityLevel.High)
- elevationRequest.Prompt = Environment.GetEnvironmentVariable("PROMPT", EnvironmentVariableTarget.User) ?? Environment.GetEnvironmentVariable("PROMPT", EnvironmentVariableTarget.Machine) ?? "$P$G";
- else if (elevationRequest.Mode != ElevationRequest.ConsoleMode.Piped || InputArguments.NewWindow)
+ if (elevationRequest.Mode != ElevationRequest.ConsoleMode.Piped || InputArguments.NewWindow)
elevationRequest.Prompt = Settings.Prompt;
else
elevationRequest.Prompt = Settings.PipedPrompt;
}
- /// Starts a cache session
- private async Task RunUsingElevatedService(ElevationRequest elevationRequest)
+ /// Starts a cache sessioBn
+ private async Task RunUsingService(ElevationRequest elevationRequest)
{
Logger.Instance.Log($"Using Console mode {elevationRequest.Mode}", LogLevel.Debug);
- var cmd = CommandToRun.FirstOrDefault();
-
Rpc.Connection connection = null;
try
- {
- connection = await ServiceHelper.ConnectStartElevatedService().ConfigureAwait(false);
-
+ {
+ var callingPid = ProcessHelper.GetCallerPid();
+
+ Logger.Instance.Log($"Caller PID: {callingPid}", LogLevel.Debug);
+
+ connection = await ServiceHelper.Connect().ConfigureAwait(false);
+
if (connection == null) // service is not running or listening.
- {
- Logger.Instance.Log("Unable to connect to the elevated service.", LogLevel.Error);
- return Constants.GSUDO_ERROR_EXITCODE;
+ {
+ var service = ServiceHelper.StartService(callingPid, singleUse: InputArguments.KillCache);
+ connection = await ServiceHelper.Connect(callingPid, service).ConfigureAwait(false);
+
+ if (connection == null) // service is not running or listening.
+ throw new ApplicationException("Unable to connect to the elevated service.");
}
var renderer = GetRenderer(connection, elevationRequest);
@@ -126,15 +133,11 @@ private async Task RunUsingElevatedService(ElevationRequest elevationReques
private static int RunWithoutService(string exeName, string args, ElevationRequest elevationRequest)
{
- Logger.Instance.Log("Already running as the specified user/permission-level. Running in-process...", LogLevel.Debug);
- var sameIntegrity = (int)InputArguments.GetIntegrityLevel() == ProcessHelper.GetCurrentIntegrityLevel();
+ var sameIntegrity = (int)InputArguments.GetIntegrityLevel() == SecurityHelper.GetCurrentIntegrityLevel();
// No need to escalate. Run in-process
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);
- if (!string.IsNullOrEmpty(elevationRequest.Prompt))
- {
- Environment.SetEnvironmentVariable("PROMPT", Environment.ExpandEnvironmentVariables(elevationRequest.Prompt));
- }
+ ConsoleHelper.SetPrompt(elevationRequest, InputArguments.GetIntegrityLevel() >= IntegrityLevel.High);
if (sameIntegrity)
{
@@ -165,7 +168,7 @@ private static int RunWithoutService(string exeName, string args, ElevationReque
}
else // lower integrity
{
- if (elevationRequest.IntegrityLevel= ProcessHelper.GetCurrentIntegrityLevel())
+ if ((int)(InputArguments.GetIntegrityLevel()) >= SecurityHelper.GetCurrentIntegrityLevel())
{
if (!elevationRequest.NewWindow)
{
@@ -217,9 +220,12 @@ internal static bool IsRunningAsDesiredUser()
if (InputArguments.RunAsSystem && !WindowsIdentity.GetCurrent().IsSystem)
return false;
- if ((int)InputArguments.GetIntegrityLevel() != ProcessHelper.GetCurrentIntegrityLevel())
+ if ((int)InputArguments.GetIntegrityLevel() != SecurityHelper.GetCurrentIntegrityLevel())
return false;
+ if (InputArguments.UserName != null && InputArguments.UserName != WindowsIdentity.GetCurrent().Name)
+ return false;
+
return true;
}
@@ -236,7 +242,10 @@ private static bool IsElevationRequired()
if (integrityLevel == IntegrityLevel.MediumRestricted)
return true;
- return (int)integrityLevel > ProcessHelper.GetCurrentIntegrityLevel();
+ if (InputArguments.UserName != null)
+ return true;
+
+ return (int)integrityLevel > SecurityHelper.GetCurrentIntegrityLevel();
}
///
@@ -244,28 +253,19 @@ private static bool IsElevationRequired()
/// or enhanced, colorfull VT mode with nice TAB auto-complete.
///
///
- private static ElevationRequest.ConsoleMode GetElevationMode(bool isWindowsApp)
- {
- if ((!ProcessHelper.IsMemberOfLocalAdmins() || // => Not local admin? Force attached mode, so the new process has admin user env vars. (See #113)
- Settings.ForceAttachedConsole) && !Settings.ForcePipedConsole && !Settings.ForceVTConsole)
- {
- if (Console.IsErrorRedirected
- || Console.IsInputRedirected
- || Console.IsOutputRedirected)
- {
- // Attached mode doesnt supports redirection.
- return ElevationRequest.ConsoleMode.Piped;
- }
- if (InputArguments.TrustedInstaller)
- return ElevationRequest.ConsoleMode.VT; // workaround for #173
-
- return ElevationRequest.ConsoleMode.Attached;
- }
-
+ private static ElevationRequest.ConsoleMode GetElevationMode()
+ {
if (Settings.ForcePipedConsole)
return ElevationRequest.ConsoleMode.Piped;
- if (Settings.ForceVTConsole)
+ // When running as other user =>
+ bool runningAsOtherUser = InputArguments.UserName != null && // Elevating as someone else, we don't want to user caller profile and just switch tokens, We want to use target user profile.
+ !SecurityHelper.IsAdministrator(); // And if caller is not elevated => attach mode works.
+
+ bool runningAsOtherUserButElevated = InputArguments.UserName != null &&
+ SecurityHelper.IsAdministrator(); // => If caller is elevated, attach mode fails if target user is not elevated, so go with VT/piped modes.
+
+ if (Settings.ForceVTConsole || runningAsOtherUserButElevated)
{
if (Console.IsErrorRedirected && Console.IsOutputRedirected)
{
@@ -283,6 +283,21 @@ private static ElevationRequest.ConsoleMode GetElevationMode(bool isWindowsApp)
return ElevationRequest.ConsoleMode.VT;
}
+ if (Settings.ForceAttachedConsole || runningAsOtherUser)
+ {
+ if (Console.IsErrorRedirected
+ || Console.IsInputRedirected
+ || Console.IsOutputRedirected)
+ {
+ // Attached mode doesnt supports redirection.
+ return ElevationRequest.ConsoleMode.Piped;
+ }
+ if (InputArguments.TrustedInstaller)
+ return ElevationRequest.ConsoleMode.VT; // workaround for #173
+
+ return ElevationRequest.ConsoleMode.Attached;
+ }
+
return ElevationRequest.ConsoleMode.TokenSwitch;
}
diff --git a/src/gsudo/Commands/ServiceCommand.cs b/src/gsudo/Commands/ServiceCommand.cs
index b96f8f35..01a9147d 100644
--- a/src/gsudo/Commands/ServiceCommand.cs
+++ b/src/gsudo/Commands/ServiceCommand.cs
@@ -7,10 +7,11 @@
using System.Runtime.Serialization.Formatters.Binary;
using System.Linq;
using gsudo.Helpers;
+using gsudo.CredentialsCache;
#if NETCOREAPP
using System.Text.Json;
#endif
-
+
namespace gsudo.Commands
{
class ServiceCommand : ICommand, IDisposable
@@ -21,7 +22,9 @@ class ServiceCommand : ICommand, IDisposable
public TimeSpan CacheDuration { get; set; }
public bool SingleUse { get; set; }
- Timer ShutdownTimer;
+ Timer ShutdownTimer;
+ private IRpcServer _server;
+
void EnableTimer()
{
if (CacheDuration != TimeSpan.MaxValue)
@@ -34,44 +37,53 @@ public async Task Execute()
{
// service mode
if (LogLvl.HasValue) Settings.LogLevel.Value = LogLvl.Value;
-
+ if (!SecurityHelper.IsMemberOfLocalAdmins()) InputArguments.IntegrityLevel = IntegrityLevel.Medium;
+
Console.Title = "gsudo Service";
- Console.WriteLine();
Commands.HelpCommand.ShowVersion();
Console.WriteLine();
+ if (InputArguments.Debug) await new StatusCommand().Execute().ConfigureAwait(false);
+ Console.WriteLine();
+ /*
if ((InputArguments.TrustedInstaller && !System.Security.Principal.WindowsIdentity.GetCurrent().Claims.Any(c => c.Value == Constants.TI_SID))
- || (InputArguments.RunAsSystem && !System.Security.Principal.WindowsIdentity.GetCurrent().IsSystem))
+ || (InputArguments.RunAsSystem && !System.Security.Principal.WindowsIdentity.GetCurrent().IsSystem)
+ || (InputArguments.UserName != null && !SecurityHelper.IsAdministrator() && SecurityHelper.IsMemberOfLocalAdmins())
+ )*/
+ if (!RunCommand.IsRunningAsDesiredUser())
{
- return Helpers.ServiceHelper.StartElevatedService(AllowedPid, CacheDuration, singleUse: SingleUse, allowedSid: AllowedSid) ? 0: Constants.GSUDO_ERROR_EXITCODE;
- }
+ Logger.Instance.Log("This service is not running with desired credentials. Starting a new service instance.", LogLevel.Info);
+ ServiceHelper.StartService(AllowedPid, CacheDuration, AllowedSid, SingleUse);
+ return 0;
+ }
- var cacheLifetime = new CredentialsCacheLifetimeManager(AllowedPid);
+ var cacheLifetime = new CredentialsCache.CredentialsCacheLifetimeManager(AllowedPid);
Logger.Instance.Log("Service started", LogLevel.Info);
if (CacheDuration == TimeSpan.Zero)
CacheDuration = TimeSpan.FromSeconds(10);
- using (IRpcServer server = CreateServer())
+ using (_server = CreateServer())
{
try
{
- cacheLifetime.OnCacheClear += server.Close;
- ShutdownTimer = new Timer((o) => server.Close(), null, Timeout.Infinite, Timeout.Infinite); // 10 seconds for initial connection or die.
- server.ConnectionAccepted += (o, connection) => AcceptConnection(connection).ConfigureAwait(false).GetAwaiter().GetResult();
- server.ConnectionClosed += (o, connection) => EnableTimer();
+ cacheLifetime.OnCacheClear += _server.Close;
+ ShutdownTimer = new Timer((o) => _server.Close(), null, Timeout.Infinite, Timeout.Infinite); // 10 seconds for initial connection or die.
+ _server.ConnectionAccepted += (o, connection) => AcceptConnection(connection).ConfigureAwait(false).GetAwaiter().GetResult();
+ _server.ConnectionClosed += (o, connection) => EnableTimer();
Logger.Instance.Log($"Service will shutdown if idle for {CacheDuration}", LogLevel.Debug);
EnableTimer();
- await server.Listen().ConfigureAwait(false);
+ await _server.Listen().ConfigureAwait(false);
}
catch (System.OperationCanceledException) { }
finally
{
- cacheLifetime.OnCacheClear -= server.Close;
+ cacheLifetime.OnCacheClear -= _server.Close;
}
}
+ _server = null;
Logger.Instance.Log("Service stopped", LogLevel.Info);
return 0;
@@ -87,17 +99,18 @@ private async Task AcceptConnection(Connection connection)
if (request.KillCache) throw new OperationCanceledException();
IProcessHost applicationHost = CreateProcessHost(request);
-
- if (!applicationHost.SupportsSimultaneousElevations && Settings.CacheMode.Value==Enums.CacheMode.Auto)
- {
- ServiceHelper.StartElevatedService(AllowedPid, CacheDuration, AllowedSid);
- }
-
- if (!string.IsNullOrEmpty(request.Prompt))
- Environment.SetEnvironmentVariable("PROMPT",
- Environment.ExpandEnvironmentVariables(request.Prompt));
-
+ bool replaceService = !applicationHost.SupportsSimultaneousElevations && Settings.CacheMode.Value == CacheMode.Auto && !SingleUse;
+
+ // This can create too many gsudo service instances when in attached mode.
+ // TODO: Maybe we can only do this if... ¿our parent PID is not gsudo?
+ if (replaceService)
+ ServiceHelper.StartService(AllowedPid, CacheDuration, AllowedSid, SingleUse);
+
+ ConsoleHelper.SetPrompt(request, connection.IsHighIntegrity);
await applicationHost.Start(connection, request).ConfigureAwait(false);
+
+ if (replaceService)
+ _server.Close();
}
catch (OperationCanceledException)
{
@@ -127,7 +140,8 @@ private static IProcessHost CreateProcessHost(ElevationRequest request)
private IRpcServer CreateServer()
{
// No credentials cache when CacheDuration = 0
- bool singleUse = SingleUse || Settings.CacheMode.Value == Enums.CacheMode.Disabled;
+
+ bool singleUse = SingleUse || Settings.CacheMode.Value == CredentialsCache.CacheMode.Disabled;
return new NamedPipeServer(AllowedPid, AllowedSid, singleUse);
}
diff --git a/src/gsudo/Commands/StatusCommand.cs b/src/gsudo/Commands/StatusCommand.cs
index 05ef02d1..31630af9 100644
--- a/src/gsudo/Commands/StatusCommand.cs
+++ b/src/gsudo/Commands/StatusCommand.cs
@@ -17,7 +17,7 @@ public Task Execute()
Console.WriteLine($"Caller Pid: {ProcessHelper.GetCallerPid()}");
var id = WindowsIdentity.GetCurrent();
- bool isAdmin = ProcessHelper.IsAdministrator();
+ bool isAdmin = SecurityHelper.IsAdministrator();
Console.Write($"Running as:\n User: ");
if (isAdmin)
@@ -31,11 +31,11 @@ public Task Execute()
if (isAdmin)
Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine(ProcessHelper.IsAdministrator());
+ Console.WriteLine(SecurityHelper.IsAdministrator());
Console.ResetColor();
Console.Write($" Integrity Level: ");
- var integrity = ProcessHelper.GetCurrentIntegrityLevel();
+ var integrity = SecurityHelper.GetCurrentIntegrityLevel();
var integrityString = string.Empty;
if (Enum.IsDefined(typeof(IntegrityLevel), integrity))
@@ -98,7 +98,7 @@ private void PrintConsoleProcessList()
try
{
- username = p.GetProcessUser() ?? unknown;
+ username = p.GetProcessUser()?.Name ?? unknown;
}
catch
{ }
diff --git a/src/gsudo/Enums/CacheMode.cs b/src/gsudo/CredentialsCache/CacheMode.cs
similarity index 62%
rename from src/gsudo/Enums/CacheMode.cs
rename to src/gsudo/CredentialsCache/CacheMode.cs
index 6fb5c7ef..e8d67ab9 100644
--- a/src/gsudo/Enums/CacheMode.cs
+++ b/src/gsudo/CredentialsCache/CacheMode.cs
@@ -1,14 +1,12 @@
-using System.ComponentModel;
-
-namespace gsudo.Enums
-{
- enum CacheMode
- {
- Disabled,
- Explicit,
- [Description("Enabling the credentials cache is a security risk.")]
- Auto,
-// [Description("Enabling the credentials cache is a security risk.")]
-// Unsafe,
- }
-}
+using System.ComponentModel;
+
+namespace gsudo.CredentialsCache
+{
+ enum CacheMode
+ {
+ Disabled,
+ Explicit,
+ [Description("Enabling the credentials cache is a security risk.")]
+ Auto,
+ }
+}
diff --git a/src/gsudo/CredentialsCacheLifetimeManager.cs b/src/gsudo/CredentialsCache/CredentialsCacheLifetimeManager.cs
similarity index 89%
rename from src/gsudo/CredentialsCacheLifetimeManager.cs
rename to src/gsudo/CredentialsCache/CredentialsCacheLifetimeManager.cs
index 0941538c..e9bc451d 100644
--- a/src/gsudo/CredentialsCacheLifetimeManager.cs
+++ b/src/gsudo/CredentialsCache/CredentialsCacheLifetimeManager.cs
@@ -6,8 +6,8 @@
#if NETFRAMEWORK
using EventWaitHandleAcl = System.Threading.EventWaitHandle;
#endif
-
-namespace gsudo
+
+namespace gsudo.CredentialsCache
{
///
/// Mechanism to invalidate all credentials cache. ('sudo -k')
@@ -55,7 +55,7 @@ public CredentialsCacheLifetimeManager(int pid)
out var eventWaitHandle))
{
#if NETFRAMEWORK
- eventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, GLOBAL_WAIT_HANDLE_NAME, out var created, security);
+ eventWaitHandle = new EventWaitHandleAcl(false, EventResetMode.ManualReset, GLOBAL_WAIT_HANDLE_NAME, out var created, security);
#else
eventWaitHandle = EventWaitHandleAcl.Create(false, EventResetMode.ManualReset, GLOBAL_WAIT_HANDLE_NAME, out var created, security);
#endif
@@ -67,7 +67,7 @@ public CredentialsCacheLifetimeManager(int pid)
out var eventWaitHandleSpecific))
{
#if NETFRAMEWORK
- eventWaitHandleSpecific = new EventWaitHandle(false, EventResetMode.ManualReset, GLOBAL_WAIT_HANDLE_NAME + pid.ToString(CultureInfo.InvariantCulture), out var created, security);
+ eventWaitHandleSpecific = new EventWaitHandleAcl(false, EventResetMode.ManualReset, GLOBAL_WAIT_HANDLE_NAME + pid.ToString(CultureInfo.InvariantCulture), out var created, security);
#else
eventWaitHandleSpecific = EventWaitHandleAcl.Create(false, EventResetMode.ManualReset, GLOBAL_WAIT_HANDLE_NAME + pid.ToString(CultureInfo.InvariantCulture), out var created, security);
#endif
@@ -75,7 +75,7 @@ public CredentialsCacheLifetimeManager(int pid)
var credentialsResetThread = new Thread(() =>
{
- if (WaitHandle.WaitAny(new WaitHandle[] {eventWaitHandle, eventWaitHandleSpecific}) >= 0)
+ if (WaitHandle.WaitAny(new WaitHandle[] { eventWaitHandle, eventWaitHandleSpecific }) >= 0)
{
Logger.Instance.Log("Credentials Cache termination received", LogLevel.Info);
OnCacheClear?.Invoke();
@@ -102,7 +102,7 @@ public static bool ClearCredentialsCache(int? pid = null)
}
return true;
}
- catch (System.Threading.WaitHandleCannotBeOpenedException)
+ catch (WaitHandleCannotBeOpenedException)
{
return false;
}
diff --git a/src/gsudo/ElevationRequest.cs b/src/gsudo/ElevationRequest.cs
index 1e545eef..37fc55ed 100644
--- a/src/gsudo/ElevationRequest.cs
+++ b/src/gsudo/ElevationRequest.cs
@@ -25,15 +25,15 @@ class ElevationRequest
[Serializable]
internal enum ConsoleMode {
///
- /// Obsolete, Process started at the service, I/O streamed via named pipes.
+ /// Process started at the service, I/O streamed via named pipes.
///
Piped,
///
- /// Obsolete, Experimental, Process started at the service using PseudoConsole, VT100 I/O streamed via named pipes.
+ /// Process started at the service using PseudoConsole, VT100 I/O streamed via named pipes.
///
VT,
///
- /// Obsolete, Process started at the service, then attached to the caller console unsing APIs.
+ /// Process started at the service, then attached to the caller console unsing APIs.
///
Attached,
///
diff --git a/src/gsudo/Helpers/CommandLineParser.cs b/src/gsudo/Helpers/CommandLineParser.cs
index 2f461744..5f65bb0f 100644
--- a/src/gsudo/Helpers/CommandLineParser.cs
+++ b/src/gsudo/Helpers/CommandLineParser.cs
@@ -3,12 +3,13 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Security.Principal;
namespace gsudo.Helpers
{
// Why not use a parsing library?
// When gsudo was built on .Net Framework 4.x, loading the parsing library took significant time at startup.
- // This may no longer be the case on modern .net, but that would require coding and comparing performance.
+ // This may no longer be the case on modern .net, but confirming that would require coding and comparing performance.
public class CommandLineParser
{
LinkedList args;
@@ -101,6 +102,11 @@ ICommand ParseOption(string argChar, string argWord, out bool skipRemainingChars
InputArguments.IntegrityLevel = ExtensionMethods.ParseEnum(optionArg);
skipRemainingChars = true;
}
+ else if (IsOptionMatchWithArgument(argWord, "u", "--user", out optionArg))
+ {
+ InputArguments.SetUserName(optionArg);
+ skipRemainingChars = true;
+ }
else if (match("n", "--new")) { InputArguments.NewWindow = true; }
else if (match("w", "--wait")) { InputArguments.Wait = true; }
else if (match("s", "--system")) { InputArguments.RunAsSystem = true; }
@@ -144,6 +150,7 @@ private ICommand ParseVerb()
&& !InputArguments.Wait
&& !InputArguments.TrustedInstaller
&& !InputArguments.Direct
+ && InputArguments.UserName == null
)
{
// support for "-k" as command
@@ -154,7 +161,7 @@ private ICommand ParseVerb()
return new KillCacheCommand(verbose: true);
}
- return new RunCommand() { CommandToRun = Array.Empty() };
+ return new RunCommand(commandToRun: Array.Empty());
}
string arg;
@@ -194,7 +201,7 @@ private ICommand ParseVerb()
while (args.Count > 0)
{
arg = DeQueueArg();
-
+
if (arg.In("on"))
cmd.Action = CacheCommandAction.On;
else if (arg.In("off"))
@@ -205,6 +212,14 @@ private ICommand ParseVerb()
{
cmd.AllowedPid = int.Parse(v, CultureInfo.InvariantCulture);
}
+ else if (IsOptionMatchWithArgument(arg, "s", "--sid", out v))
+ {
+ cmd.AllowedSid = v;
+ }
+ else if (IsOptionMatchWithArgument(arg, "u", "--user", out v))
+ {
+ InputArguments.SetUserName(v);
+ }
else if (IsOptionMatchWithArgument(arg, "d", "--duration", out v))
{
cmd.CacheDuration = Settings.TimeSpanParseWithInfinite(v);
@@ -219,14 +234,17 @@ private ICommand ParseVerb()
}
if (arg.In("run"))
- return new RunCommand() { CommandToRun = args.ToArray() };
+ return new RunCommand(commandToRun: args.ToArray());
+
+ if (arg.In("AttachRun"))
+ return new AttachRunCommand(commandToRun: args.ToArray());
args.AddFirst(arg);
if (arg == "!!" || arg.StartsWith("!", StringComparison.InvariantCulture))
return new BangBangCommand() { Pattern = string.Join(" ", args) };
- return new RunCommand() { CommandToRun = args.ToArray() };
+ return new RunCommand(commandToRun: args.ToArray());
}
#region Posix option matching functions
diff --git a/src/gsudo/Helpers/CommandToRunGenerator.cs b/src/gsudo/Helpers/CommandToRunGenerator.cs
index 8ddca4ff..b46c05f4 100644
--- a/src/gsudo/Helpers/CommandToRunGenerator.cs
+++ b/src/gsudo/Helpers/CommandToRunGenerator.cs
@@ -35,6 +35,9 @@ internal static string[] AugmentCommand(string[] args)
string currentShellExeName = ShellHelper.InvokingShellFullPath;
Shell currentShell = ShellHelper.InvokingShell;
+ if (currentShellExeName[0] != '"' && currentShellExeName.Contains(' '))
+ { currentShellExeName = $"\"{currentShellExeName}\""; }
+
Logger.Instance.Log($"Invoking Shell: {currentShell}", LogLevel.Debug);
if (!InputArguments.Direct)
@@ -63,7 +66,7 @@ Running ./gsudo {command} should elevate the powershell command.
var newArgs = new List
{
- $"\"{currentShellExeName}\""
+ currentShellExeName
};
newArgs.AddMany(args);
@@ -73,7 +76,7 @@ Running ./gsudo {command} should elevate the powershell command.
{
var newArgs = new List
{
- $"\"{currentShellExeName}\"",
+ currentShellExeName,
"-NoLogo"
};
@@ -168,7 +171,7 @@ Running ./gsudo {command} should elevate the powershell command.
if (currentShell != Shell.Cmd)
{
// Let's find Cmd.Exe
- currentShellExeName = Environment.GetEnvironmentVariable("COMSPEC");
+ currentShellExeName = $"\"{Environment.GetEnvironmentVariable("COMSPEC")}\"";
}
if (args.Length == 0)
diff --git a/src/gsudo/Helpers/ConsoleHelper.cs b/src/gsudo/Helpers/ConsoleHelper.cs
index 1605ce15..43b2c9b7 100644
--- a/src/gsudo/Helpers/ConsoleHelper.cs
+++ b/src/gsudo/Helpers/ConsoleHelper.cs
@@ -2,12 +2,18 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
+using System.Security;
using static gsudo.Native.ConsoleApi;
namespace gsudo.Helpers
{
class ConsoleHelper
{
+ static ConsoleHelper()
+ {
+ IgnoreConsoleCancelKeyPress += IgnoreConsoleCancelKeyPressMethod; // ensure no garbage collection
+ }
+
public static bool EnableVT()
{
var hStdOut = Native.ConsoleApi.GetStdHandle(Native.ConsoleApi.STD_OUTPUT_HANDLE);
@@ -38,11 +44,6 @@ private static bool IgnoreConsoleCancelKeyPressMethod(CtrlTypes ctrlType)
return false;
}
- static ConsoleHelper()
- {
- IgnoreConsoleCancelKeyPress += IgnoreConsoleCancelKeyPressMethod;
- }
-
public static uint[] GetConsoleAttachedPids()
{
var processIds = new uint[1];
@@ -86,6 +87,43 @@ public static void GetConsoleInfo(out int width, out int height, out int cursorL
cursorLeftPos = Console.CursorLeft;
cursorTopPos = Console.CursorTop;
}
- }
+ }
+
+ internal static SecureString ReadConsolePassword(string userName)
+ {
+ Console.Error.Write($"Password for user {userName}: ");
+
+ var pass = new SecureString();
+ ConsoleKey key;
+ do
+ {
+ var keyInfo = Console.ReadKey(intercept: true);
+ key = keyInfo.Key;
+
+ if (key == ConsoleKey.Backspace && pass.Length > 0)
+ {
+ Console.Error.Write("\b \b");
+ pass.RemoveAt(pass.Length - 1);
+ }
+ else if (!char.IsControl(keyInfo.KeyChar))
+ {
+ Console.Error.Write("*");
+ pass.AppendChar(keyInfo.KeyChar);
+ }
+ } while (key != ConsoleKey.Enter);
+ Console.Error.Write("\n");
+ return pass;
+ }
+
+ internal static void SetPrompt(ElevationRequest elevationRequest, bool isElevated)
+ {
+ if (!string.IsNullOrEmpty(elevationRequest.Prompt))
+ {
+ if (!isElevated)
+ Environment.SetEnvironmentVariable("PROMPT", Environment.GetEnvironmentVariable("PROMPT", EnvironmentVariableTarget.User) ?? Environment.GetEnvironmentVariable("PROMPT", EnvironmentVariableTarget.Machine) ?? "$P$G");
+ else
+ Environment.SetEnvironmentVariable("PROMPT", Environment.ExpandEnvironmentVariables(elevationRequest.Prompt));
+ }
+ }
}
}
diff --git a/src/gsudo/Helpers/LoginHelper.cs b/src/gsudo/Helpers/LoginHelper.cs
new file mode 100644
index 00000000..a9891931
--- /dev/null
+++ b/src/gsudo/Helpers/LoginHelper.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Security.Principal;
+
+namespace gsudo.Helpers
+{
+ internal static class LoginHelper
+ {
+ internal static string ValidateUserName(string userName)
+ {
+ try
+ {
+ return new NTAccount(userName).Translate(typeof(SecurityIdentifier)).Translate(typeof(NTAccount)).Value;
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationException($"Value \"{userName}\" is not a valid Username.", ex );
+ }
+ }
+
+ internal static string GetSidFromUserName(string userName)
+ {
+ try
+ {
+ return new NTAccount(userName).Translate(typeof(SecurityIdentifier)).Value;
+ }
+ catch (Exception ex)
+ {
+ throw new ApplicationException($"Value \"{userName}\" is not a valid Username.", ex);
+ }
+ }
+
+ }
+}
diff --git a/src/gsudo/Helpers/ProcessFactory.cs b/src/gsudo/Helpers/ProcessFactory.cs
index 12ada022..ac3dbd19 100644
--- a/src/gsudo/Helpers/ProcessFactory.cs
+++ b/src/gsudo/Helpers/ProcessFactory.cs
@@ -8,13 +8,12 @@
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Security;
using static gsudo.Native.ProcessApi;
using static gsudo.Native.TokensApi;
namespace gsudo.Helpers
{
- //https://csharp.hotexamples.com/examples/CSCreateLowIntegrityProcess/PROCESS_INFORMATION/-/php-process_information-class-examples.html
-
public static class ProcessFactory
{
public static Process StartElevatedDetached(string filename, string arguments, bool hidden)
@@ -58,6 +57,7 @@ public static Process StartRedirected(string fileName, string arguments, string
public static Process StartAttached(string filename, string arguments)
{
+ Logger.Instance.Log($"Process Start: {filename} {arguments}", LogLevel.Debug );
var process = new Process();
process.StartInfo = new ProcessStartInfo(filename)
{
@@ -99,6 +99,34 @@ public static Process StartDetached(string filename, string arguments, string st
return process;
}
+ public static Process StartWithCredentials(string filename, string arguments, string user, SecureString password)
+ {
+ Logger.Instance.Log($"Starting process as {user}: {filename} {arguments}", LogLevel.Debug);
+ var usr = InputArguments.UserName.Split('\\');
+
+ try
+ {
+ return Process.Start(new ProcessStartInfo()
+ {
+ UserName = usr[1],
+ Domain = usr[0],
+ Password = password,
+ Arguments = arguments,
+ FileName = filename,
+ LoadUserProfile = true,
+ UseShellExecute = false,
+ CreateNoWindow = !InputArguments.Debug,
+ });
+ }
+ catch(Win32Exception ex)
+ {
+ if (ex.NativeErrorCode == 1326)
+ throw new ApplicationException("The user name or password is incorrect.");
+
+ throw;
+ }
+ }
+
public static bool IsWindowsApp(string exe)
{
var path = FindExecutableInPath(ArgumentsHelper.UnQuote(exe));
@@ -184,7 +212,7 @@ public static SafeProcessHandle StartAttachedWithIntegrity(IntegrityLevel integr
{
// must return a process Handle because we cant create a Process() from a handle and get the exit code.
Logger.Instance.Log($"{nameof(StartAttachedWithIntegrity)}: {appToRun} {args}", LogLevel.Debug);
- int currentIntegrity = ProcessHelper.GetCurrentIntegrityLevel();
+ int currentIntegrity = SecurityHelper.GetCurrentIntegrityLevel();
SafeTokenHandle newToken;
if ((int)integrityLevel == currentIntegrity)
diff --git a/src/gsudo/Helpers/ProcessHelper.cs b/src/gsudo/Helpers/ProcessHelper.cs
index 78dbeade..628474eb 100644
--- a/src/gsudo/Helpers/ProcessHelper.cs
+++ b/src/gsudo/Helpers/ProcessHelper.cs
@@ -7,14 +7,11 @@
using System.Security.Principal;
using System.Threading;
using gsudo.Native;
-using System.Linq;
namespace gsudo.Helpers
{
- public static class ProcessHelper
+ internal static class ProcessHelper
{
- private static int? _cacheGetCurrentIntegrityLevelCache;
- private static bool? _cacheIsAdmin;
private static string _cacheOwnExeName;
public static string GetOwnExeName()
@@ -36,15 +33,14 @@ public static string GetExeName(this Process process)
return exeName;
}
- public static string GetProcessUser(this Process process)
+ public static WindowsIdentity GetProcessUser(this Process process)
{
IntPtr processHandle = IntPtr.Zero;
try
{
OpenProcessToken(process.Handle, 8, out processHandle);
WindowsIdentity wi = new WindowsIdentity(processHandle);
- string user = wi.Name;
- return user; //.Contains(@"\") ? user.Substring(user.IndexOf(@"\", StringComparison.OrdinalIgnoreCase) + 1) : user;
+ return wi;
}
catch
{
@@ -73,10 +69,9 @@ public static Process GetShellProcess(this Process process)
public static int GetCacheableRootProcessId(this Process process)
{
- var parent = GetParentProcessId(process);
-
if (ShellHelper.InvokingShell == Shell.Bash)
{
+ var parent = GetParentProcessId(process);
if (parent == 0)
return process.Id;
@@ -97,10 +92,32 @@ public static int GetCacheableRootProcessId(this Process process)
{ }
return parent;
+ }
- }
+ int pid = process.Id;
+ Process p = null;
+
+ while (p == null)
+ {
+ pid = GetParentProcessId(pid);
+ try
+ {
+ p = Process.GetProcessById(pid);
+ }
+ catch
+ { }
+
+ if (p != null)
+ {
+ string filename = null;
+ try { filename = p.MainModule.FileName; } catch { }
+
+ if (filename != null && !IsShim(filename))
+ break;
+ }
+ }
- return GetParentProcessIdExcludingShim(process);
+ return pid;
}
public static Process GetParentProcessExcludingShim(this Process process)
@@ -130,7 +147,7 @@ public static int GetParentProcessIdExcludingShim(this Process process)
if (IsShim(filename))
return GetParentProcessIdExcludingShim(parent);
}
- catch (Exception ex)
+ catch
{
// For example: System.ArgumentException: Process with an Id of 18312 is not running.
return parentId;
@@ -139,6 +156,7 @@ public static int GetParentProcessIdExcludingShim(this Process process)
return parentId;
}
+ // This function is special because it can get the parent process of a process that no longer exists.
public static int GetParentProcessId(int pid)
{
IntPtr hProcess = ProcessApi.OpenProcess(ProcessApi.PROCESS_QUERY_INFORMATION, true, (uint)pid);
@@ -178,35 +196,6 @@ internal static int GetCallerPid()
return Process.GetCurrentProcess().GetCacheableRootProcessId();
}
- public static bool IsHighIntegrity()
- {
- return ProcessHelper.GetCurrentIntegrityLevel() >= (int)IntegrityLevel.High;
- }
-
- public static bool IsAdministrator()
- {
- if (_cacheIsAdmin.HasValue) return _cacheIsAdmin.Value;
-
- try
- {
- WindowsIdentity identity = WindowsIdentity.GetCurrent();
- WindowsPrincipal principal = new WindowsPrincipal(identity);
- _cacheIsAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
- return _cacheIsAdmin.Value;
- }
- catch (Exception)
- {
- return false;
- }
- }
-
- public static bool IsMemberOfLocalAdmins()
- {
- var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
- var claims = principal.Claims;
- return claims.Any(c => c.Value == "S-1-5-32-544");
- }
-
public static void Terminate(this Process process)
{
if (process.HasExited) return;
@@ -236,36 +225,14 @@ public static AutoResetEvent GetProcessWaitHandle(IntPtr processHandle) =>
SafeWaitHandle = new SafeWaitHandle(processHandle, ownsHandle: false)
};
+ public static SafeProcessHandle GetSafeProcessHandle(this Process p) => new SafeProcessHandle(p.Handle, true);
+
public static AutoResetEvent GetProcessWaitHandle(this SafeProcessHandle processHandle) =>
new AutoResetEvent(false)
{
SafeWaitHandle = new SafeWaitHandle(processHandle.DangerousGetHandle(), ownsHandle: false)
};
- ///
- /// The function gets the integrity level of the current process.
- ///
- ///
- /// Returns the integrity level of the current process. It is usually one of
- /// these values:
- ///
- /// SECURITY_MANDATORY_UNTRUSTED_RID - means untrusted level
- /// SECURITY_MANDATORY_LOW_RID - means low integrity level.
- /// SECURITY_MANDATORY_MEDIUM_RID - means medium integrity level.
- /// SECURITY_MANDATORY_HIGH_RID - means high integrity level.
- /// SECURITY_MANDATORY_SYSTEM_RID - means system integrity level.
- ///
- ///
- ///
- /// When any native Windows API call fails, the function throws a Win32Exception
- /// with the last error code.
- ///
- static internal int GetCurrentIntegrityLevel()
- {
- if (_cacheGetCurrentIntegrityLevelCache.HasValue) return _cacheGetCurrentIntegrityLevelCache.Value;
-
- return GetProcessIntegrityLevel(ProcessApi.GetCurrentProcess());
- }
static internal int GetProcessIntegrityLevel(IntPtr processHandle)
{
@@ -363,7 +330,7 @@ static internal int GetProcessIntegrityLevel(IntPtr processHandle)
}
}
- return (_cacheGetCurrentIntegrityLevelCache = IL).Value;
+ return IL;
}
///
diff --git a/src/gsudo/Helpers/SecurityHelper.cs b/src/gsudo/Helpers/SecurityHelper.cs
new file mode 100644
index 00000000..2377d2e3
--- /dev/null
+++ b/src/gsudo/Helpers/SecurityHelper.cs
@@ -0,0 +1,67 @@
+using gsudo.Native;
+using System;
+using System.Linq;
+using System.Security.Principal;
+
+namespace gsudo.Helpers
+{
+ internal static class SecurityHelper
+ {
+ public static bool IsMemberOfLocalAdmins()
+ {
+ var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
+ var claims = principal.Claims;
+ return claims.Any(c => c.Value == "S-1-5-32-544");
+ }
+
+ private static int? _cacheGetCurrentIntegrityLevelCache;
+ public static bool IsHighIntegrity()
+ {
+ return GetCurrentIntegrityLevel() >= (int)IntegrityLevel.High;
+ }
+
+ ///
+ /// The function gets the integrity level of the current process.
+ ///
+ ///
+ /// Returns the integrity level of the current process. It is usually one of
+ /// these values:
+ ///
+ /// SECURITY_MANDATORY_UNTRUSTED_RID - means untrusted level
+ /// SECURITY_MANDATORY_LOW_RID - means low integrity level.
+ /// SECURITY_MANDATORY_MEDIUM_RID - means medium integrity level.
+ /// SECURITY_MANDATORY_HIGH_RID - means high integrity level.
+ /// SECURITY_MANDATORY_SYSTEM_RID - means system integrity level.
+ ///
+ ///
+ ///
+ /// When any native Windows API call fails, the function throws a Win32Exception
+ /// with the last error code.
+ ///
+ static internal int GetCurrentIntegrityLevel()
+ {
+ if (_cacheGetCurrentIntegrityLevelCache.HasValue) return _cacheGetCurrentIntegrityLevelCache.Value;
+ _cacheGetCurrentIntegrityLevelCache = ProcessHelper.GetProcessIntegrityLevel(ProcessApi.GetCurrentProcess());
+
+ return _cacheGetCurrentIntegrityLevelCache.Value;
+ }
+
+ private static bool? _cacheIsAdmin;
+ public static bool IsAdministrator()
+ {
+ if (_cacheIsAdmin.HasValue) return _cacheIsAdmin.Value;
+
+ try
+ {
+ WindowsIdentity identity = WindowsIdentity.GetCurrent();
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ _cacheIsAdmin = principal.IsInRole(WindowsBuiltInRole.Administrator);
+ return _cacheIsAdmin.Value;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/gsudo/Helpers/ServiceHelper.cs b/src/gsudo/Helpers/ServiceHelper.cs
index ff517547..2f5f3dcc 100644
--- a/src/gsudo/Helpers/ServiceHelper.cs
+++ b/src/gsudo/Helpers/ServiceHelper.cs
@@ -1,7 +1,9 @@
using gsudo.Rpc;
+using Microsoft.Win32.SafeHandles;
using System;
using System.Diagnostics;
using System.Linq;
+using System.Security;
using System.Security.Principal;
using System.Threading.Tasks;
@@ -9,71 +11,54 @@ namespace gsudo.Helpers
{
internal static class ServiceHelper
{
- internal static bool IsServiceAvailable()
- {
- return NamedPipeClient.IsServiceAvailable();
- }
-
internal static IRpcClient GetRpcClient()
{
// future Tcp implementations should be plugged here.
return new NamedPipeClient();
}
- internal static async Task ConnectStartElevatedService()
+ internal static async Task Connect(int? callingPid = null, SafeProcessHandle serviceHandle = null)
{
- var callingPid = ProcessHelper.GetCallerPid();
-
- Logger.Instance.Log($"Caller PID: {callingPid}", LogLevel.Debug);
-
- if (InputArguments.IntegrityLevel.HasValue && InputArguments.IntegrityLevel.Value == IntegrityLevel.System && !InputArguments.RunAsSystem)
- {
- Logger.Instance.Log($"Elevating as System because of IntegrityLevel=System parameter.", LogLevel.Warning);
- InputArguments.RunAsSystem = true;
- }
-
IRpcClient rpcClient = GetRpcClient();
- Connection connection = null;
try
{
- connection = await rpcClient.Connect(null, true).ConfigureAwait(false);
+ return await rpcClient.Connect(callingPid, serviceHandle).ConfigureAwait(false);
}
catch (System.IO.IOException) { }
catch (TimeoutException) { }
catch (Exception ex)
{
- Logger.Instance.Log(ex.ToString(), LogLevel.Warning);
+ if (callingPid.HasValue)
+ Logger.Instance.Log(ex.ToString(), LogLevel.Warning);
}
- if (connection == null) // service is not running or listening.
- {
- if (!StartElevatedService(callingPid, singleUse: InputArguments.KillCache))
- return null;
-
- connection = await rpcClient.Connect(callingPid, false).ConfigureAwait(false);
- }
- return connection;
+ return null;
}
- internal static bool StartElevatedService(int? allowedPid, TimeSpan? cacheDuration = null, string allowedSid=null, bool singleUse = false)
+ internal static SafeProcessHandle StartService(int? allowedPid, TimeSpan? cacheDuration = null, string allowedSid = null, bool singleUse = false)
{
- var callingSid = allowedSid ?? System.Security.Principal.WindowsIdentity.GetCurrent().User.Value;
- var callingPid = allowedPid ?? Process.GetCurrentProcess().GetCacheableRootProcessId();
+ var currentSid = WindowsIdentity.GetCurrent().User.Value;
+
+ allowedPid = allowedPid ?? Process.GetCurrentProcess().GetCacheableRootProcessId();
+ allowedSid = allowedSid ?? Process.GetProcessById(allowedPid.Value)?.GetProcessUser()?.User.Value ?? currentSid;
+
string verb;
+ SafeProcessHandle ret;
- Logger.Instance.Log($"Caller SID: {callingSid}", LogLevel.Debug);
+ Logger.Instance.Log($"Caller SID: {allowedSid}", LogLevel.Debug);
- var @params = InputArguments.Debug ? "--debug " : string.Empty;
- // if (InputArguments.IntegrityLevel.HasValue) @params += $"-i {InputArguments.IntegrityLevel.Value} ";
- if (InputArguments.RunAsSystem && allowedSid != System.Security.Principal.WindowsIdentity.GetCurrent().User.Value) @params += "-s ";
+ var @params = InputArguments.Debug ? "--debug " : string.Empty;
+ if (!InputArguments.RunAsSystem && InputArguments.IntegrityLevel.HasValue) @params += $"-i {InputArguments.IntegrityLevel.Value} ";
+ if (InputArguments.RunAsSystem && allowedSid != currentSid) @params += "-s ";
if (InputArguments.TrustedInstaller) @params += "--ti ";
+ if (InputArguments.UserName != null) @params += $"-u {InputArguments.UserName} ";
verb = "gsudoservice";
if (!cacheDuration.HasValue || singleUse)
- {
- if (!Settings.CacheMode.Value.In(Enums.CacheMode.Auto) || singleUse)
+ {
+ if (!Settings.CacheMode.Value.In(CredentialsCache.CacheMode.Auto) || singleUse)
{
verb = "gsudoelevate";
cacheDuration = TimeSpan.Zero;
@@ -82,45 +67,45 @@ internal static bool StartElevatedService(int? allowedPid, TimeSpan? cacheDurati
cacheDuration = Settings.CacheDuration;
}
- bool isAdmin = ProcessHelper.IsHighIntegrity();
-
- string commandLine = $"{@params}{verb} {callingPid} {callingSid} {Settings.LogLevel} {Settings.TimeSpanWithInfiniteToString(cacheDuration.Value)}";
-
- bool success = false;
-
- try
- {
- string ownExe = ProcessHelper.GetOwnExeName();
- if (InputArguments.TrustedInstaller && isAdmin && !WindowsIdentity.GetCurrent().Claims.Any(c => c.Value == Constants.TI_SID))
- {
- return StartTrustedInstallerService(commandLine, callingPid);
- }
- else if (InputArguments.RunAsSystem && isAdmin)
- {
- success = null != ProcessFactory.StartAsSystem(ownExe, commandLine, Environment.CurrentDirectory, !InputArguments.Debug);
- }
- else
- {
- success = null != ProcessFactory.StartElevatedDetached(ownExe, commandLine, !InputArguments.Debug);
- }
- }
- catch (System.ComponentModel.Win32Exception ex)
- {
- Logger.Instance.Log(ex.Message, LogLevel.Error);
- return false;
- }
-
- if (!success)
- {
- Logger.Instance.Log("Failed to start elevated instance.", LogLevel.Error);
- return false;
+ bool isAdmin = SecurityHelper.IsHighIntegrity();
+
+ string commandLine = $"{@params}{verb} {allowedPid} {allowedSid} {Settings.LogLevel} {Settings.TimeSpanWithInfiniteToString(cacheDuration.Value)}";
+
+ string ownExe = ProcessHelper.GetOwnExeName();
+ if (InputArguments.TrustedInstaller && isAdmin && !WindowsIdentity.GetCurrent().Claims.Any(c => c.Value == Constants.TI_SID))
+ {
+ StartTrustedInstallerService(commandLine, allowedPid.Value);
+ ret = null;
+ }
+ else if (InputArguments.RunAsSystem && isAdmin)
+ {
+ ret = ProcessFactory.StartAsSystem(ownExe, commandLine, Environment.CurrentDirectory, !InputArguments.Debug);
+ }
+ else if (InputArguments.UserName != null)
+ {
+ if (InputArguments.UserName != WindowsIdentity.GetCurrent().Name)
+ {
+ var password = ConsoleHelper.ReadConsolePassword(InputArguments.UserName);
+ ret = ProcessFactory.StartWithCredentials(ownExe, commandLine, InputArguments.UserName, password).GetSafeProcessHandle();
+ }
+ else
+ {
+ if (SecurityHelper.IsMemberOfLocalAdmins() && InputArguments.GetIntegrityLevel() >= IntegrityLevel.High)
+ ret = ProcessFactory.StartElevatedDetached(ownExe, commandLine, !InputArguments.Debug).GetSafeProcessHandle();
+ else
+ ret = ProcessFactory.StartDetached(ownExe, commandLine, null, !InputArguments.Debug).GetSafeProcessHandle();
+ }
+ }
+ else
+ {
+ ret = ProcessFactory.StartElevatedDetached(ownExe, commandLine, !InputArguments.Debug).GetSafeProcessHandle();
}
- Logger.Instance.Log("Elevated instance started.", LogLevel.Debug);
- return true;
+ Logger.Instance.Log("Service process started.", LogLevel.Debug);
+ return ret;
}
- private static bool StartTrustedInstallerService(string commandLine, int pid)
+ private static void StartTrustedInstallerService(string commandLine, int pid)
{
string name = $"gsudo TI Cache for PID {pid}";
@@ -133,7 +118,7 @@ private static bool StartTrustedInstallerService(string commandLine, int pid)
: ProcessFactory.StartRedirected("schtasks", args, null);
p.WaitForExit();
- if (p.ExitCode != 0) return false;
+ if (p.ExitCode != 0) throw new ApplicationException($"Error creating a scheduled task for TrustedInstaller: {p.ExitCode}");
try
{
@@ -142,7 +127,7 @@ private static bool StartTrustedInstallerService(string commandLine, int pid)
? ProcessFactory.StartAttached("schtasks", args)
: ProcessFactory.StartRedirected("schtasks", args, null);
p.WaitForExit();
- if (p.ExitCode != 0) return false;
+ if (p.ExitCode != 0) throw new ApplicationException($"Error starting scheduled task for TrustedInstaller: {p.ExitCode}");
}
finally
{
@@ -152,8 +137,6 @@ private static bool StartTrustedInstallerService(string commandLine, int pid)
: ProcessFactory.StartRedirected("schtasks", args, null);
p.WaitForExit();
}
-
- return true;
}
}
}
diff --git a/src/gsudo/InputParameters.cs b/src/gsudo/InputParameters.cs
index ccafadb1..615b7d30 100644
--- a/src/gsudo/InputParameters.cs
+++ b/src/gsudo/InputParameters.cs
@@ -1,20 +1,44 @@
-namespace gsudo
+using gsudo.Helpers;
+
+namespace gsudo
{
public static class InputArguments
{
+ // Show debug info
public static bool Debug { get; internal set; }
+
+ // Open in new window
public static bool NewWindow { get; internal set; }
+
+ // Wait for new process to end
public static bool Wait { get; internal set; }
- public static bool RunAsSystem { get; internal set; }
+
+ // In `gsudo --global config Key Value` --global means save as machine setting.
public static bool Global { get; internal set; }
+
+ // Kill credentials cache after running.
public static bool KillCache { get; internal set; }
+
+ // Skip shell detection and asume called from CMD.
public static bool Direct { get; internal set; }
- public static IntegrityLevel? IntegrityLevel { get; internal set; }
- public static bool TrustedInstaller { get; internal set; }
- public static IntegrityLevel GetIntegrityLevel() => (RunAsSystem ? gsudo.IntegrityLevel.System : IntegrityLevel ?? gsudo.IntegrityLevel.High);
+ // Target Integrity Level
+ public static IntegrityLevel? IntegrityLevel { get; internal set; }
+
+ // Elevate as "NT Authority\System"
+ public static bool RunAsSystem { get; internal set; }
- internal static void Clear()
+ // Elevate as "NT Authority\System" but member of "NT SERVICE\TrustedInstaller" group (run whoami /groups)
+ public static bool TrustedInstaller { get; internal set; }
+
+ // User to Impersonate
+ public static string UserName { get; private set; }
+ // SID of User to Impersonate
+ public static string UserSid { get; private set; }
+
+ public static IntegrityLevel GetIntegrityLevel() => (RunAsSystem ? gsudo.IntegrityLevel.System : IntegrityLevel ?? gsudo.IntegrityLevel.High);
+
+ internal static void Clear() // added for tests repeatability
{
Debug = false;
NewWindow = false;
@@ -25,6 +49,14 @@ internal static void Clear()
Direct = false;
TrustedInstaller = false;
IntegrityLevel = null;
- }
+ UserName = null;
+ UserSid = null;
+ }
+
+ internal static void SetUserName(string username)
+ {
+ UserName = LoginHelper.ValidateUserName(username);
+ UserSid = LoginHelper.GetSidFromUserName(UserName);
+ }
}
}
diff --git a/src/gsudo/Native/ProcessApi.cs b/src/gsudo/Native/ProcessApi.cs
index d4e1b83b..77bb7406 100644
--- a/src/gsudo/Native/ProcessApi.cs
+++ b/src/gsudo/Native/ProcessApi.cs
@@ -230,5 +230,8 @@ internal static extern bool CheckRemoteDebuggerPresent(
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
}
}
diff --git a/src/gsudo/Native/TokensApi.cs b/src/gsudo/Native/TokensApi.cs
index 66edd77a..7b86315d 100644
--- a/src/gsudo/Native/TokensApi.cs
+++ b/src/gsudo/Native/TokensApi.cs
@@ -64,10 +64,10 @@ public enum LogonFlags
public enum SECURITY_IMPERSONATION_LEVEL
{
- SecurityAnonymous,
- SecurityIdentification,
- SecurityImpersonation,
- SecurityDelegation
+ SecurityAnonymous = 0,
+ SecurityIdentification = 1,
+ SecurityImpersonation = 2,
+ SecurityDelegation = 3
}
public enum TOKEN_TYPE
diff --git a/src/gsudo/ProcessHosts/AttachedConsoleHost.cs b/src/gsudo/ProcessHosts/AttachedConsoleHost.cs
index 0524fb62..d18b02aa 100644
--- a/src/gsudo/ProcessHosts/AttachedConsoleHost.cs
+++ b/src/gsudo/ProcessHosts/AttachedConsoleHost.cs
@@ -1,4 +1,5 @@
using System;
+using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using gsudo.Helpers;
@@ -30,10 +31,18 @@ public async Task Start(Connection connection, ElevationRequest elevationRequest
if (Native.ConsoleApi.AttachConsole(pid))
{
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);
- System.Environment.CurrentDirectory = elevationRequest.StartFolder;
try
{
+ try
+ {
+ System.Environment.CurrentDirectory = elevationRequest.StartFolder;
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ throw new ApplicationException($"User \"{WindowsIdentity.GetCurrent().Name}\" can not access current directory \"{elevationRequest.StartFolder}\"");
+ }
+
var process = Helpers.ProcessFactory.StartAttached(elevationRequest.FileName, elevationRequest.Arguments);
WaitHandle.WaitAny(new WaitHandle[] { process.GetProcessWaitHandle(), connection.DisconnectedWaitHandle });
@@ -42,9 +51,14 @@ public async Task Start(Connection connection, ElevationRequest elevationRequest
await Task.Delay(1).ConfigureAwait(false);
}
+ catch (ApplicationException ex)
+ {
+ await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error: {ex.Message}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false);
+ exitCode = Constants.GSUDO_ERROR_EXITCODE;
+ }
catch (Exception ex)
{
- await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error:{ex.ToString()}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false);
+ await connection.ControlStream.WriteAsync($"{Constants.TOKEN_ERROR}Server Error: {ex.ToString()}\r\n{Constants.TOKEN_ERROR}").ConfigureAwait(false);
exitCode = Constants.GSUDO_ERROR_EXITCODE;
}
}
diff --git a/src/gsudo/ProcessRenderers/AttachedConsoleRenderer.cs b/src/gsudo/ProcessRenderers/AttachedConsoleRenderer.cs
index 5045fadc..16e2b6e5 100644
--- a/src/gsudo/ProcessRenderers/AttachedConsoleRenderer.cs
+++ b/src/gsudo/ProcessRenderers/AttachedConsoleRenderer.cs
@@ -5,7 +5,6 @@
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Text;
using System.Threading.Tasks;
namespace gsudo.ProcessRenderers
diff --git a/src/gsudo/ProcessRenderers/TokenSwitchRenderer.cs b/src/gsudo/ProcessRenderers/TokenSwitchRenderer.cs
index 30da34c0..c96700a9 100644
--- a/src/gsudo/ProcessRenderers/TokenSwitchRenderer.cs
+++ b/src/gsudo/ProcessRenderers/TokenSwitchRenderer.cs
@@ -29,8 +29,8 @@ internal TokenSwitchRenderer(Connection connection, ElevationRequest elevationRe
throw new Exception("TokenSwitch mode not supported when SecurityEnforceUacIsolation is set.");
_connection = connection;
- _elevationRequest = elevationRequest;
- Environment.SetEnvironmentVariable("prompt", Environment.ExpandEnvironmentVariables(elevationRequest.Prompt));
+ _elevationRequest = elevationRequest;
+ ConsoleHelper.SetPrompt(elevationRequest, connection.IsHighIntegrity);
ProcessApi.CreateProcessFlags dwCreationFlags = ProcessApi.CreateProcessFlags.CREATE_SUSPENDED;
diff --git a/src/gsudo/Program.cs b/src/gsudo/Program.cs
index 79dafd9b..63b5f1bd 100644
--- a/src/gsudo/Program.cs
+++ b/src/gsudo/Program.cs
@@ -19,13 +19,18 @@ private static async Task Start()
ICommand cmd = null;
var commandLine = ArgumentsHelper.GetRealCommandLine();
- Logger.Instance.Log($"Command Line: {commandLine}", LogLevel.Debug);
-
var args = ArgumentsHelper.SplitArgs(commandLine);
try
- {
- cmd = new CommandLineParser(args).Parse();
+ {
+ try
+ {
+ cmd = new CommandLineParser(args).Parse();
+ }
+ finally
+ {
+ Logger.Instance.Log($"Command Line: {commandLine}", LogLevel.Debug);
+ }
if (cmd != null)
{
diff --git a/src/gsudo/Properties/AssemblyAttributes.cs b/src/gsudo/Properties/AssemblyAttributes.cs
new file mode 100644
index 00000000..b4e4d4e0
--- /dev/null
+++ b/src/gsudo/Properties/AssemblyAttributes.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("gsudo.Tests")]
\ No newline at end of file
diff --git a/src/gsudo/Rpc/Connection.cs b/src/gsudo/Rpc/Connection.cs
index 0344dfbf..9245db9d 100644
--- a/src/gsudo/Rpc/Connection.cs
+++ b/src/gsudo/Rpc/Connection.cs
@@ -14,11 +14,12 @@ class Connection : IDisposable
{
private PipeStream _dataStream;
private PipeStream _controlStream;
-
- public Connection(PipeStream ControlStream, PipeStream DataStream)
+ public bool IsHighIntegrity { get; }
+ public Connection(PipeStream ControlStream, PipeStream DataStream, bool isHighIntegrity)
{
_dataStream = DataStream;
_controlStream = ControlStream;
+ IsHighIntegrity = isHighIntegrity;
}
public Stream DataStream => _dataStream;
diff --git a/src/gsudo/Rpc/IRpcClient.cs b/src/gsudo/Rpc/IRpcClient.cs
index ace29969..8d097cc8 100644
--- a/src/gsudo/Rpc/IRpcClient.cs
+++ b/src/gsudo/Rpc/IRpcClient.cs
@@ -1,9 +1,10 @@
-using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+using System.Threading.Tasks;
namespace gsudo.Rpc
{
internal interface IRpcClient
{
- Task Connect(int? clientPid, bool failFast);
+ Task Connect(int? clientPid = null, SafeProcessHandle serviceHandle = null);
}
}
\ No newline at end of file
diff --git a/src/gsudo/Rpc/NamedPipeClient.cs b/src/gsudo/Rpc/NamedPipeClient.cs
index 44c1cc30..00acaca7 100644
--- a/src/gsudo/Rpc/NamedPipeClient.cs
+++ b/src/gsudo/Rpc/NamedPipeClient.cs
@@ -1,4 +1,6 @@
using gsudo.Helpers;
+using gsudo.Native;
+using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
@@ -8,12 +10,13 @@ namespace gsudo.Rpc
{
class NamedPipeClient : IRpcClient
{
- public async Task Connect(int? clientPid, bool failFast)
+ public async Task Connect(int? clientPid, SafeProcessHandle serviceProcessHandle)
{
- int timeoutMilliseconds = failFast ? 300 : 5000;
+ int timeoutMilliseconds;
var server = ".";
string pipeName = null;
+ bool isHighIntegrity;
string user = System.Security.Principal.WindowsIdentity.GetCurrent().User.Value;
NamedPipeClientStream dataPipe = null;
NamedPipeClientStream controlPipe = null;
@@ -22,27 +25,38 @@ public async Task Connect(int? clientPid, bool failFast)
{
if (clientPid.HasValue)
{
- pipeName = NamedPipeNameFactory.GetPipeName(user, clientPid.Value);
- if (!NamedPipeUtils.ExistsNamedPipe(pipeName) && failFast)
- {
- // fail fast without timeout.
- return null;
- }
+ int retryLefts = 3;
+ do
+ {
+ if (ProcessApi.WaitForSingleObject(serviceProcessHandle.DangerousGetHandle(), 1) == 0) // original service process is dead, but may have started an elevated service that we don't have handle.
+ retryLefts--;
+
+ pipeName = FindService(user, clientPid.Value, out isHighIntegrity);
+
+ if (pipeName == null)
+ await Task.Delay(50).ConfigureAwait(false);
+ }
+ while (pipeName == null && retryLefts>0);
+
+ timeoutMilliseconds = 5000; // service just started. Larger Timeout
}
else
{
+ isHighIntegrity = false;
+ timeoutMilliseconds = 300;
var callerProcessId = Process.GetCurrentProcess().Id;
int maxRecursion = 20;
while (callerProcessId > 0 && maxRecursion-- > 0)
{
- callerProcessId = ProcessHelper.GetParentProcessId(callerProcessId);
- pipeName = NamedPipeNameFactory.GetPipeName(user, callerProcessId);
- // Does the pipe exists?
- if (NamedPipeUtils.ExistsNamedPipe(pipeName))
- break;
+ callerProcessId = ProcessHelper.GetParentProcessId(callerProcessId);
+
+ // Search for Credentials Cache
+
+ //Try Admin
+ pipeName = FindService(user, callerProcessId, out isHighIntegrity);
- pipeName = null;
- // try grandfather.
+ if (pipeName!=null)
+ break;
}
}
@@ -56,7 +70,7 @@ public async Task Connect(int? clientPid, bool failFast)
Logger.Instance.Log($"Connected via Named Pipe {pipeName}.", LogLevel.Debug);
- var conn = new Connection(controlPipe, dataPipe);
+ var conn = new Connection(controlPipe, dataPipe, isHighIntegrity);
return conn;
}
catch (System.TimeoutException)
@@ -71,29 +85,59 @@ public async Task Connect(int? clientPid, bool failFast)
controlPipe?.Dispose();
throw;
}
- }
-
- public static bool IsServiceAvailable(int? pid = null, string sid = null)
+ }
+
+ public static string FindService(string allowedSid, int allowedPid, out bool isHighIntegrity, string targetUserSid = null)
+ {
+ targetUserSid = targetUserSid ?? InputArguments.UserSid;
+ string pipeName;
+
+ if (!InputArguments.IntegrityLevel.HasValue || InputArguments.IntegrityLevel.Value >= IntegrityLevel.High)
+ {
+ pipeName = NamedPipeNameFactory.GetPipeName(allowedSid, allowedPid, targetUserSid, true);
+ if (NamedPipeUtils.ExistsNamedPipe(pipeName))
+ {
+ isHighIntegrity = true;
+ InputArguments.IntegrityLevel = InputArguments.IntegrityLevel ?? IntegrityLevel.High;
+ return pipeName;
+ }
+ }
+
+ if (!InputArguments.IntegrityLevel.HasValue || InputArguments.IntegrityLevel.Value < IntegrityLevel.High)
+ {
+ pipeName = NamedPipeNameFactory.GetPipeName(allowedSid, allowedPid, targetUserSid, false);
+ if (NamedPipeUtils.ExistsNamedPipe(pipeName))
+ {
+ isHighIntegrity = false;
+ InputArguments.IntegrityLevel = InputArguments.IntegrityLevel ?? IntegrityLevel.Low;
+ return pipeName;
+ }
+ }
+
+ isHighIntegrity = false;
+ return null;
+ }
+
+ public static bool IsServiceAvailable(int? allowedPid = null, string allowedSid = null, string targetSid = null)
{
string pipeName = null;
- pid = pid ?? ProcessHelper.GetParentProcessId(Process.GetCurrentProcess().Id);
- sid = sid ?? System.Security.Principal.WindowsIdentity.GetCurrent().User.Value;
-
- int maxRecursion = 20;
- while (pid.Value > 0 && maxRecursion-- > 0)
+ allowedPid = allowedPid ?? ProcessHelper.GetParentProcessId(Process.GetCurrentProcess().Id);
+ allowedSid = allowedSid ?? System.Security.Principal.WindowsIdentity.GetCurrent().User.Value;
+ targetSid = targetSid ?? InputArguments.UserSid ?? allowedSid;
+
+ int maxIterations = 20;
+ while (allowedPid.Value > 0 && maxIterations-- > 0)
{
- pipeName = NamedPipeNameFactory.GetPipeName(sid, pid.Value);
- // Does the pipe exists?
- if (NamedPipeUtils.ExistsNamedPipe(pipeName))
+ pipeName = NamedPipeClient.FindService(allowedSid, allowedPid.Value, out _, targetSid);
+ if (pipeName != null)
break;
- pid = ProcessHelper.GetParentProcessId(pid.Value);
- pipeName = null;
+ allowedPid = ProcessHelper.GetParentProcessId(allowedPid.Value);
// try grandfather.
}
- return pipeName != null ;
+ return pipeName != null;
}
}
}
\ No newline at end of file
diff --git a/src/gsudo/Rpc/NamedPipeNameFactory.cs b/src/gsudo/Rpc/NamedPipeNameFactory.cs
index cff246f7..362b5b6c 100644
--- a/src/gsudo/Rpc/NamedPipeNameFactory.cs
+++ b/src/gsudo/Rpc/NamedPipeNameFactory.cs
@@ -1,18 +1,25 @@
-using System.Globalization;
+using gsudo.Helpers;
+using System;
+using System.Globalization;
+using System.Security.Principal;
using System.Text;
namespace gsudo.Rpc
{
static class NamedPipeNameFactory
{
- public static string GetPipeName(string connectingUser, int connectingPid)
+ public static string GetPipeName(string allowedSid, int allowedPid, string targetSid, bool isAdmin)
{
- if (connectingPid < 0) connectingPid = 0;
- var data = $"{connectingUser}_{connectingPid}{(InputArguments.TrustedInstaller ? "_TI" : string.Empty)}";
+ if (allowedPid < 0) allowedPid = 0;
+
+ var ti = InputArguments.TrustedInstaller ? "_TI" : string.Empty;
+ var admin = !isAdmin ? "_NonAdmin" : string.Empty;
+
+ var data = $"{allowedSid}_{targetSid}_{allowedPid}_{ti}{admin}";
#if !DEBUG
data = GetHash(data);
#endif
- return $"{GetPipePrefix()}_{data}";
+ return $"{GetPipePrefix(isAdmin)}_{data}";
}
private static string GetHash(string data)
@@ -29,12 +36,14 @@ private static string GetHash(string data)
}
}
- private static string GetPipePrefix()
- {
-// if ((InputArguments.GetIntegrityLevel()) >= IntegrityLevel.High)
- return "ProtectedPrefix\\Administrators\\gsudo";
-// else
-// return "gsudo";
- }
+ private static string GetPipePrefix(bool isAdmin)
+ {
+ const string PROTECTED = "ProtectedPrefix\\Administrators\\gsudo";
+ const string REGULAR = "gsudo";
+ if (isAdmin)
+ return PROTECTED;
+ else
+ return REGULAR;
+ }
}
}
diff --git a/src/gsudo/Rpc/NamedPipeServer.cs b/src/gsudo/Rpc/NamedPipeServer.cs
index 9cb49461..3e2c4777 100644
--- a/src/gsudo/Rpc/NamedPipeServer.cs
+++ b/src/gsudo/Rpc/NamedPipeServer.cs
@@ -79,7 +79,9 @@ public async Task Listen()
PipeAccessRights.FullControl,
System.Security.AccessControl.AccessControlType.Deny));
- var pipeName = NamedPipeNameFactory.GetPipeName(_allowedSid, _allowedPid);
+ bool isHighIntegrity = SecurityHelper.IsHighIntegrity();
+
+ var pipeName = NamedPipeNameFactory.GetPipeName(_allowedSid, _allowedPid, InputArguments.UserSid, isHighIntegrity);
Logger.Instance.Log($"Listening on named pipe {pipeName}.", LogLevel.Debug);
Logger.Instance.Log($"Access allowed only for ProcessID {_allowedPid} and children", LogLevel.Debug);
@@ -116,7 +118,7 @@ public async Task Listen()
if (dataPipe.IsConnected && controlPipe.IsConnected && !_cancellationTokenSource.IsCancellationRequested)
{
- var connection = new Connection(controlPipe, dataPipe);
+ var connection = new Connection(controlPipe, dataPipe, isHighIntegrity);
ConnectionKeepAliveThread.Start(connection);
@@ -171,26 +173,33 @@ private bool IsAuthorized(int originalClientPid, int allowedPid)
ProcessModule clientProcessMainModule = null;
clientProcess = Process.GetProcessById(clientPid);
- clientProcessMainModule = clientProcess.MainModule;
-
- if (_allowedExeLength != -1)
- {
- var callingExe = SymbolicLinkSupport.ResolveSymbolicLink(clientProcessMainModule.FileName);
- var fileInfo = new System.IO.FileInfo(callingExe);
- var callingExeTimeStamp = fileInfo.LastWriteTimeUtc;
- var callingExeLength = fileInfo.Length;
-
- if (callingExe != _allowedExe || callingExeLength != _allowedExeLength ||
- callingExeTimeStamp != _allowedExeTimeStamp)
- {
- // I'm not checking the SHA because it would be too slow.
- Logger.Instance.Log(
- $"Invalid Client. Rejecting Connection. \nAllowed: {_allowedExe}\nActual: {callingExe}",
- LogLevel.Error);
- return false;
- }
- }
+ if (SecurityHelper.GetCurrentIntegrityLevel() <= (int)IntegrityLevel.Medium)
+ {
+ // not much to protect.
+ return true;
+ }
+
+ clientProcessMainModule = clientProcess.MainModule;
+
+ if (_allowedExeLength != -1)
+ {
+ var callingExe = SymbolicLinkSupport.ResolveSymbolicLink(clientProcessMainModule.FileName);
+ var fileInfo = new System.IO.FileInfo(callingExe);
+ var callingExeTimeStamp = fileInfo.LastWriteTimeUtc;
+ var callingExeLength = fileInfo.Length;
+
+ if (callingExe != _allowedExe || callingExeLength != _allowedExeLength ||
+ callingExeTimeStamp != _allowedExeTimeStamp)
+ {
+ // I'm not checking the SHA because it would be too slow.
+
+ Logger.Instance.Log(
+ $"Invalid Client. Rejecting Connection. \nAllowed: {_allowedExe}\nActual: {callingExe}",
+ LogLevel.Error);
+ return false;
+ }
+ }
#if !DEBUG
if (clientProcessMainModule != null)
{
diff --git a/src/gsudo/Rpc/NamedPipeUtils.cs b/src/gsudo/Rpc/NamedPipeUtils.cs
index f2b96930..fd9fc049 100644
--- a/src/gsudo/Rpc/NamedPipeUtils.cs
+++ b/src/gsudo/Rpc/NamedPipeUtils.cs
@@ -34,9 +34,6 @@ public static bool ExistsNamedPipe(string name)
var namedPipes = new List();
Native.FileApi.WIN32_FIND_DATA lpFindFileData;
-#if DEBUG
- Logger.Instance.Log($"Looking for Named Pipe \"{name}\".", LogLevel.Debug);
-#endif
var ptr = Native.FileApi.FindFirstFile($@"\\.\pipe\{GetRootFolder(name)}*", out lpFindFileData);
do
{
diff --git a/src/gsudo/RegistrySetting.cs b/src/gsudo/Settings/RegistrySetting.cs
similarity index 100%
rename from src/gsudo/RegistrySetting.cs
rename to src/gsudo/Settings/RegistrySetting.cs
diff --git a/src/gsudo/Settings.cs b/src/gsudo/Settings/Settings.cs
similarity index 97%
rename from src/gsudo/Settings.cs
rename to src/gsudo/Settings/Settings.cs
index c5a54f56..f3d9e523 100644
--- a/src/gsudo/Settings.cs
+++ b/src/gsudo/Settings/Settings.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text;
-using gsudo.Enums;
+using gsudo.CredentialsCache;
using Microsoft.Win32;
namespace gsudo
@@ -15,7 +15,7 @@ class Settings
public const int BufferSize = 10240;
public static readonly Encoding Encoding = new System.Text.UTF8Encoding(false);
public static RegistrySetting CacheMode { get; set; }
- = new RegistrySetting(nameof(CacheMode), Enums.CacheMode.Explicit,
+ = new RegistrySetting(nameof(CacheMode), CredentialsCache.CacheMode.Explicit,
deserializer: ExtensionMethods.ParseEnum< CacheMode>,
scope: RegistrySettingScope.GlobalOnly);
@@ -90,16 +90,21 @@ class Settings
CacheMode,
CacheDuration,
LogLevel,
+
Prompt,
PipedPrompt,
+
ForceAttachedConsole,
ForcePipedConsole,
ForceVTConsole,
+
CopyEnvironmentVariables,
CopyNetworkShares,
+
PowerShellLoadProfile,
SecurityEnforceUacIsolation,
- ExceptionList);
+ ExceptionList
+ );
internal static TimeSpan TimeSpanParseWithInfinite(string value)
{
diff --git a/src/gsudo/Tokens/IntegrityLevel.cs b/src/gsudo/Tokens/IntegrityLevel.cs
index e374ecfa..b8d079eb 100644
--- a/src/gsudo/Tokens/IntegrityLevel.cs
+++ b/src/gsudo/Tokens/IntegrityLevel.cs
@@ -1,31 +1,31 @@
-using static gsudo.Native.TokensApi;
-
-namespace gsudo
-{
- public enum IntegrityLevel
- {
- Untrusted = 0,
- Low = 4096,
- Medium = 8192,
- MediumRestricted = 8193, // Experimental, like medium but with a token flagged as elevated. It can't elevate again using RunAs.
- MediumPlus = 8448,
- High = 12288,
- System = 16384,
- Protected = 20480,
- Secure = 28672
- }
-
- static class IntegrityLevelExtensions
- {
- public static SaferLevels ToSaferLevel(this IntegrityLevel integrityLevel)
- {
- if (integrityLevel >= IntegrityLevel.High)
- return SaferLevels.FullyTrusted;
- if (integrityLevel >= IntegrityLevel.Medium)
- return SaferLevels.NormalUser;
- if (integrityLevel >= IntegrityLevel.Low)
- return SaferLevels.Constrained;
- return SaferLevels.Untrusted;
- }
- }
+using static gsudo.Native.TokensApi;
+
+namespace gsudo
+{
+ public enum IntegrityLevel
+ {
+ Untrusted = 0,
+ Low = 4096,
+ Medium = 8192,
+ MediumRestricted = 8193, // Experimental, like medium but without the split token. It can call RunAs but won't elevate.
+ MediumPlus = 8448,
+ High = 12288,
+ System = 16384,
+ Protected = 20480,
+ Secure = 28672
+ }
+
+ static class IntegrityLevelExtensions
+ {
+ public static SaferLevels ToSaferLevel(this IntegrityLevel integrityLevel)
+ {
+ if (integrityLevel >= IntegrityLevel.High)
+ return SaferLevels.FullyTrusted;
+ if (integrityLevel >= IntegrityLevel.Medium)
+ return SaferLevels.NormalUser;
+ if (integrityLevel >= IntegrityLevel.Low)
+ return SaferLevels.Constrained;
+ return SaferLevels.Untrusted;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/gsudo/Tokens/NativeMethods.cs b/src/gsudo/Tokens/NativeMethods.cs
index 0fd4545a..e71ae796 100644
--- a/src/gsudo/Tokens/NativeMethods.cs
+++ b/src/gsudo/Tokens/NativeMethods.cs
@@ -1,74 +1,104 @@
-using System;
-using System.Runtime.InteropServices;
-
-namespace gsudo.Tokens
-{
- internal static partial class NativeMethods
- {
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);
-
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool AdjustTokenPrivileges(IntPtr tokenhandle,
- [MarshalAs(UnmanagedType.Bool)] bool disableAllPrivileges,
- [MarshalAs(UnmanagedType.Struct)]ref TOKEN_PRIVILEGES newstate,
- uint bufferlength, IntPtr previousState, IntPtr returnlength);
-
- internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
-
- internal const int ERROR_NOT_ALL_ASSIGNED = 1300;
-
- internal const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
- internal const UInt32 STANDARD_RIGHTS_READ = 0x00020000;
- internal const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
- internal const UInt32 TOKEN_DUPLICATE = 0x0002;
- internal const UInt32 TOKEN_IMPERSONATE = 0x0004;
- internal const UInt32 TOKEN_QUERY = 0x0008;
- internal const UInt32 TOKEN_QUERY_SOURCE = 0x0010;
- internal const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
- internal const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;
- internal const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
- internal const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
- internal const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
- internal const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
- TOKEN_ASSIGN_PRIMARY |
- TOKEN_DUPLICATE |
- TOKEN_IMPERSONATE |
- TOKEN_QUERY |
- TOKEN_QUERY_SOURCE |
- TOKEN_ADJUST_PRIVILEGES |
- TOKEN_ADJUST_GROUPS |
- TOKEN_ADJUST_DEFAULT |
- TOKEN_ADJUST_SESSIONID);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- internal static extern IntPtr GetCurrentProcess();
-
- [DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern bool OpenProcessToken(IntPtr processHandle,
- uint desiredAccesss,
- out IntPtr tokenHandle);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- internal static extern Boolean CloseHandle(IntPtr hObject);
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct LUID
- {
- internal Int32 LowPart;
- internal UInt32 HighPart;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- internal struct TOKEN_PRIVILEGES
- {
- internal Int32 PrivilegeCount;
- internal LUID Luid;
- internal Int32 Attributes;
- }
- }
-}
+using gsudo.Native;
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using static gsudo.Native.TokensApi;
+
+namespace gsudo.Tokens
+{
+ internal static partial class NativeMethods
+ {
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid);
+
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint Bufferlength, IntPtr PreviousState, IntPtr ReturnLength);
+
+ internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
+ internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
+ internal const int ERROR_NOT_ALL_ASSIGNED = 0x00000514;
+
+ internal const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
+ internal const UInt32 STANDARD_RIGHTS_READ = 0x00020000;
+ internal const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
+ internal const UInt32 TOKEN_DUPLICATE = 0x0002;
+ internal const UInt32 TOKEN_IMPERSONATE = 0x0004;
+ internal const UInt32 TOKEN_QUERY = 0x0008;
+ internal const UInt32 TOKEN_QUERY_SOURCE = 0x0010;
+ internal const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x0020;
+ internal const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;
+ internal const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
+ internal const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
+ internal const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
+ internal const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
+ TOKEN_ASSIGN_PRIMARY |
+ TOKEN_DUPLICATE |
+ TOKEN_IMPERSONATE |
+ TOKEN_QUERY |
+ TOKEN_QUERY_SOURCE |
+ TOKEN_ADJUST_PRIVILEGES |
+ TOKEN_ADJUST_GROUPS |
+ TOKEN_ADJUST_DEFAULT |
+ TOKEN_ADJUST_SESSIONID);
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ internal static extern IntPtr GetCurrentProcess();
+
+ [DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool OpenProcessToken(IntPtr processHandle,
+ uint desiredAccesss,
+ out IntPtr tokenHandle);
+
+ [DllImport("Advapi32.dll", SetLastError = true)]
+ internal static extern bool OpenThreadToken(IntPtr ThreadToken, TokenAccessLevels DesiredAccess, bool OpenAsSelf, out SafeTokenHandle TokenHandle);
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ internal static extern IntPtr GetCurrentThread();
+
+ [DllImport("Advapi32.dll", SetLastError = true)]
+ public static extern bool SetThreadToken(IntPtr Thread, SafeTokenHandle Token);
+
+
+ [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern Boolean CloseHandle(IntPtr hObject);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LUID
+ {
+ private uint lowPart;
+ private int highPart;
+
+ public uint LowPart { get => lowPart; set => lowPart = value; }
+
+ public int HighPart { get => highPart; set => highPart = value; }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct LUID_AND_ATTRIBUTES
+ {
+ private LUID luid;
+ private uint attributes;
+
+ public LUID Luid { get => luid; set => luid = value; }
+
+ public uint Attributes { get => attributes; set => attributes = value; }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TOKEN_PRIVILEGES
+ {
+ private uint privilegeCount;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
+ private LUID_AND_ATTRIBUTES[] privileges;
+
+ public uint PrivilegeCount { get => privilegeCount; set => privilegeCount = value; }
+
+ public LUID_AND_ATTRIBUTES[] Privileges { get => privileges; set => privileges = value; }
+ }
+ }
+}
diff --git a/src/gsudo/Tokens/PriviledgeManager.cs b/src/gsudo/Tokens/PriviledgeManager.cs
deleted file mode 100644
index cd34062e..00000000
--- a/src/gsudo/Tokens/PriviledgeManager.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.ComponentModel;
-
-namespace gsudo.Tokens
-{
- //Enable a privilege by implementing the following line in your code:
- //Privileges.EnablePrivilege(SecurityEntity.SE_SHUTDOWN_NAME);
-
- //Needed code:
- public static class PrivilegesManager
- {
- public static void DisableAllPrivileges(IntPtr tokenHandle)
- {
- var TOKEN_PRIVILEGES = new NativeMethods.TOKEN_PRIVILEGES();
-
- if (!NativeMethods.AdjustTokenPrivileges(tokenHandle, true, ref TOKEN_PRIVILEGES, 1024, IntPtr.Zero, IntPtr.Zero))
- throw new Win32Exception();
- }
-
- }
-
-
-}
\ No newline at end of file
diff --git a/src/gsudo/Tokens/PrivilegeManager.cs b/src/gsudo/Tokens/PrivilegeManager.cs
new file mode 100644
index 00000000..a6334ec9
--- /dev/null
+++ b/src/gsudo/Tokens/PrivilegeManager.cs
@@ -0,0 +1,74 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Security.Principal;
+using static gsudo.Native.TokensApi;
+using static gsudo.Tokens.NativeMethods;
+
+namespace gsudo.Tokens
+{
+ //Enable a privilege in the current thread by implementing the following line in your code:
+ //PrivilegeManager.EnablePrivilege(SecurityEntity.SE_SHUTDOWN_NAME);
+
+ public static class PrivilegeManager
+ {
+ public static void DisableAllPrivileges(IntPtr tokenHandle)
+ {
+ var TOKEN_PRIVILEGES = new NativeMethods.TOKEN_PRIVILEGES();
+
+ if (!NativeMethods.AdjustTokenPrivileges(tokenHandle, true, ref TOKEN_PRIVILEGES, 0, IntPtr.Zero, IntPtr.Zero))
+ throw new Win32Exception();
+ }
+
+ public static void SetPrivilegeState(Privilege securityEntity, bool enabled)
+ {
+ const int ERROR_NO_TOKEN = 0x3f0;
+
+ var locallyUniqueIdentifier = new LUID();
+
+ if (!LookupPrivilegeValue(null, securityEntity.ToString(), ref locallyUniqueIdentifier))
+ throw new Win32Exception();
+
+ if (!OpenThreadToken(GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, out var token))
+ {
+ var error = Marshal.GetLastWin32Error();
+ if (error != ERROR_NO_TOKEN)
+ {
+ throw new Win32Exception(error);
+ }
+
+ // No token is on the thread, copy from process
+ if (!OpenProcessToken(GetCurrentProcess(), (uint)TokenAccessLevels.Duplicate, out var processToken))
+ {
+ throw new Win32Exception();
+ }
+
+ if (!DuplicateTokenEx(processToken, (uint)(TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges),
+ IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenImpersonation, out token))
+ {
+ throw new Win32Exception();
+ }
+
+ if (!SetThreadToken(IntPtr.Zero, token))
+ {
+ throw new Win32Exception();
+ }
+ }
+
+ var tp = new NativeMethods.TOKEN_PRIVILEGES();
+ tp.PrivilegeCount = 1;
+
+ tp.Privileges = new LUID_AND_ATTRIBUTES[1];
+ tp.Privileges[0].Attributes = (uint) (enabled ? NativeMethods.SE_PRIVILEGE_ENABLED : NativeMethods.SE_PRIVILEGE_DISABLED);
+ tp.Privileges[0].Luid = locallyUniqueIdentifier;
+
+ if (!NativeMethods.AdjustTokenPrivileges(token.DangerousGetHandle(), false, ref tp, (uint)Marshal.SizeOf(tp),
+ IntPtr.Zero, IntPtr.Zero))
+ throw new Win32Exception();
+
+ Logger.Instance.Log($"Privilege {securityEntity} was {(enabled ? "ENABLED" : "DISABLED")}", LogLevel.Warning);
+ token.Close();
+ // todo: proper close.
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/gsudo/Tokens/TokenProvider.cs b/src/gsudo/Tokens/TokenProvider.cs
index 174205f0..55bad5f9 100644
--- a/src/gsudo/Tokens/TokenProvider.cs
+++ b/src/gsudo/Tokens/TokenProvider.cs
@@ -114,6 +114,13 @@ public static TokenProvider CreateFromCurrentProcessToken(uint access =
{
var tm = OpenCurrentProcessToken(access);
return tm.Duplicate(MAXIMUM_ALLOWED);
+ }
+
+ public static TokenProvider CreateFromLogin(string user, string password)
+ {
+ throw new NotImplementedException();
+ var tm = new TokenProvider();
+ return tm.Duplicate(MAXIMUM_ALLOWED);
}
public TokenProvider Duplicate(uint desiredAccess = 0x02000000)
@@ -199,7 +206,7 @@ public TokenProvider GetLinkedToken(uint desiredAccess = MAXIMUM_ALLOWED)
internal static TokenProvider CreateUnelevated(IntegrityLevel level)
{
- if (ProcessHelper.IsAdministrator())
+ if (SecurityHelper.IsAdministrator())
{
// Have you impersonated system first?
if (!WindowsIdentity.GetCurrent().IsSystem)
@@ -339,16 +346,19 @@ public TokenProvider EnablePrivilege(Privilege securityEntity, bool throwOnFailu
if (!NativeMethods.LookupPrivilegeValue(null, securityEntity.ToString(), ref locallyUniqueIdentifier))
throw new Win32Exception();
- var TOKEN_PRIVILEGES = new NativeMethods.TOKEN_PRIVILEGES();
- TOKEN_PRIVILEGES.PrivilegeCount = 1;
- TOKEN_PRIVILEGES.Attributes = NativeMethods.SE_PRIVILEGE_ENABLED;
- TOKEN_PRIVILEGES.Luid = locallyUniqueIdentifier;
+ var tp = new NativeMethods.TOKEN_PRIVILEGES();
+ tp.PrivilegeCount = 1;
+ tp.Privileges = new NativeMethods.LUID_AND_ATTRIBUTES[1];
+ tp.Privileges[0].Attributes = NativeMethods.SE_PRIVILEGE_ENABLED;
+ tp.Privileges[0].Luid = locallyUniqueIdentifier;
- if (!NativeMethods.AdjustTokenPrivileges(Token.DangerousGetHandle(), false, ref TOKEN_PRIVILEGES, 1024,
- IntPtr.Zero, IntPtr.Zero))
+ if (!NativeMethods.AdjustTokenPrivileges(Token.DangerousGetHandle(), false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
if (throwOnFailure)
- throw new Win32Exception();
-
+ throw new Win32Exception();
+
+ if (throwOnFailure && ConsoleApi.GetLastError() != 0)
+ throw new Win32Exception(); //throw new Exception("The token does not have the specified privilege. \n");
+
return this;
}
diff --git a/src/gsudo/Tokens/TokenSwitcher.cs b/src/gsudo/Tokens/TokenSwitcher.cs
index fd565478..2510bc6d 100644
--- a/src/gsudo/Tokens/TokenSwitcher.cs
+++ b/src/gsudo/Tokens/TokenSwitcher.cs
@@ -18,9 +18,14 @@ public static void ReplaceProcessToken(ElevationRequest elevationRequest)
tokenInfo.Token = desiredToken.DangerousGetHandle();
tokenInfo.Thread = IntPtr.Zero;
+ // We need System account to replace process
+ // To set an elevated process token we don't need to impersonate System...
+ // But to set a System token to a process, we do need SeAssignPrimaryTokenPrivilege.
TokenProvider
.CreateFromSystemAccount()
- .EnablePrivilege(Privilege.SeAssignPrimaryTokenPrivilege, true)
+ .EnablePrivilege(Privilege.SeAssignPrimaryTokenPrivilege, false)
+ .EnablePrivilege(Privilege.SeTcbPrivilege, false)
+ .EnablePrivilege(Privilege.SeIncreaseQuotaPrivilege, false)
.Impersonate(() =>
{
IntPtr hProcess = ProcessApi.OpenProcess(ProcessApi.PROCESS_SET_INFORMATION, true,
@@ -91,7 +96,7 @@ private static SafeTokenHandle GetDesiredToken(ElevationRequest elevationRequest
else
{
tm = TokenProvider.CreateFromCurrentProcessToken(TokenProvider.MAXIMUM_ALLOWED);
- if (ProcessHelper.GetCurrentIntegrityLevel() != (int)elevationRequest.IntegrityLevel)
+ if (SecurityHelper.GetCurrentIntegrityLevel() != (int)elevationRequest.IntegrityLevel)
{
tm.SetIntegrity(elevationRequest.IntegrityLevel);
}