Skip to content

Commit

Permalink
Refactor ConsoleApi (part 1) to use CsWin32 PInvoke code generator: U…
Browse files Browse the repository at this point in the history
…pdate type usages, replace direct DLL imports with PInvoke calls, and adjust process ID types from int to uint for consistency with Win32 API.
  • Loading branch information
gerardog committed Feb 27, 2024
1 parent cf887bf commit 7b78ac8
Show file tree
Hide file tree
Showing 19 changed files with 103 additions and 106 deletions.
27 changes: 27 additions & 0 deletions docs/docs/usage/scripts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
::sidebar_position: 1
id: scripts
title: Usage on scripts
hide_title: true
---
## Scripts

This folder contains sample scripts demonstrating how to use `gsudo` to elevate privileges on a Windows machine. The provided scripts include:

1. **Script Self-Elevation:** A technique to ensure that the current script is running elevated, by detecting when it is not, and elevating itself. More details can be found in the [gsudo documentation on script self-elevation](https://gerardog.github.io/gsudo/docs/tips/script-self-elevation).

- [`self-elevate.cmd`](./self-elevate.cmd): Batch script version
- [`self-elevate-one-liner.cmd`](./self-elevate-one-liner.cmd): A one-liner version of the batch script.
- [`self-elevate.ps1`](./self-elevate.ps1): PowerShell version.
- [`self-elevate-without-gsudo.cmd`](./self-elevate-without-gsudo.cmd): Example without using `gsudo`.

2. **Many Elevations Using gsudo Cache:**

- [`many-elevations-using-gsudo-cache.cmd`](./many-elevations-using-gsudo-cache.cmd): Batch script version
- [`many-elevations-using-gsudo-cache.ps1`](./many-elevations-using-gsudo-cache.ps1): PowerShell version

3. **Don't show an UAC pop-up:** Perform an elevated admin task if and only if it can be done without an interactive UAC pop-up. (i.e. when already elevated or gsudo cache is active)
- [`silent-elevation-one-liner.cmd`](./silent-elevation-one-liner.cmd): A one-liner version.
- [`silent-elevation.cmd`](./silent-elevation.cmd): Verbose version .

These scripts are examples and should be used with caution. Always review and understand the code you are executing on your system.
13 changes: 7 additions & 6 deletions src/gsudo/Commands/CtrlCCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,30 @@
using System;
using System.Globalization;
using System.Threading.Tasks;
using Windows.Win32;
using static gsudo.Native.ConsoleApi;

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; }
public uint Pid { get; set; }

public bool SendSigBreak { get; set; }

public Task<int> Execute()
{
FreeConsole();
PInvoke.FreeConsole();

if (AttachConsole(Pid))
if (PInvoke.AttachConsole(Pid))
{
if (SendSigBreak)
GenerateConsoleCtrlEvent(CtrlTypes.CTRL_BREAK_EVENT, 0);
PInvoke.GenerateConsoleCtrlEvent((uint)CtrlTypes.CTRL_BREAK_EVENT, 0);
else
GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);
PInvoke.GenerateConsoleCtrlEvent((uint)CtrlTypes.CTRL_C_EVENT, 0);

FreeConsole();
PInvoke.FreeConsole();
}
else
{
Expand Down
7 changes: 4 additions & 3 deletions src/gsudo/Commands/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;

using Windows.Win32;

namespace gsudo.Commands
{
public class RunCommand : ICommand
Expand Down Expand Up @@ -62,7 +63,7 @@ public async Task<int> Execute()
NewWindow = InputArguments.NewWindow,
Wait = (!commandBuilder.IsWindowsApp && !InputArguments.NewWindow) || InputArguments.Wait,
Mode = elevationMode,
ConsoleProcessId = Process.GetCurrentProcess().Id,
ConsoleProcessId = (uint) Process.GetCurrentProcess().Id,
IntegrityLevel = InputArguments.GetIntegrityLevel(),
ConsoleWidth = consoleWidth,
ConsoleHeight = consoleHeight,
Expand Down Expand Up @@ -154,7 +155,7 @@ private static int RunWithoutService(ElevationRequest elevationRequest)
{
var sameIntegrity = (int)InputArguments.GetIntegrityLevel() == SecurityHelper.GetCurrentIntegrityLevel();
// No need to escalate. Run in-process
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);
PInvoke.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);

ConsoleHelper.SetPrompt(elevationRequest);

Expand Down
2 changes: 1 addition & 1 deletion src/gsudo/ElevationRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ElevationRequest
public int ConsoleWidth { get; set; }
public int ConsoleHeight { get; set; }
public ConsoleMode Mode { get; set; }
public int ConsoleProcessId { get; set; }
public uint ConsoleProcessId { get; set; }
public int TargetProcessId { get; set; }
public bool KillCache { get; set; }
public IntegrityLevel IntegrityLevel { get; set; }
Expand Down
4 changes: 2 additions & 2 deletions src/gsudo/Helpers/ArgumentsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Windows.Win32;

namespace gsudo.Helpers
{
Expand Down Expand Up @@ -40,8 +41,7 @@ public static IList<string> SplitArgs(string args)

internal static string GetRealCommandLine()
{
System.IntPtr ptr = ConsoleApi.GetCommandLine();
string commandLine = Marshal.PtrToStringAuto(ptr).TrimStart();
string commandLine = PInvoke.GetCommandLine().ToString().TrimStart();

if (commandLine[0] == '"')
return commandLine.Substring(commandLine.IndexOf('"', 1) + 1).TrimStart(' ');
Expand Down
2 changes: 1 addition & 1 deletion src/gsudo/Helpers/CommandLineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private ICommand ParseVerb()
if (arg.In("gsudoctrlc"))
return new CtrlCCommand()
{
Pid = int.Parse(DeQueueArg(), CultureInfo.InvariantCulture),
Pid = uint.Parse(DeQueueArg(), CultureInfo.InvariantCulture),
SendSigBreak = bool.Parse(DeQueueArg())
};

Expand Down
27 changes: 17 additions & 10 deletions src/gsudo/Helpers/ConsoleHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,34 @@
using System.Runtime.InteropServices;
using System.Security;
using static gsudo.Native.ConsoleApi;

using Windows.Win32;
using Windows.Win32.System.Console;
using Microsoft.Win32.SafeHandles;
using Windows.Win32.Foundation;

namespace gsudo.Helpers
{
class ConsoleHelper
{
{
const UInt32 ATTACH_PARENT_PROCESS = 0xFFFFFFFF;

Check warning on line 16 in src/gsudo/Helpers/ConsoleHelper.cs

View workflow job for this annotation

GitHub Actions / Test

Unused field 'ATTACH_PARENT_PROCESS'. (https://docs.microsoft.com/visualstudio/code-quality/ca1823-avoid-unused-private-fields)

static ConsoleHelper()
{
IgnoreConsoleCancelKeyPress += IgnoreConsoleCancelKeyPressMethod; // ensure no garbage collection
}

public static bool EnableVT()
public static unsafe bool EnableVT()
{
var hStdOut = Native.ConsoleApi.GetStdHandle(Native.ConsoleApi.STD_OUTPUT_HANDLE);
if (!Native.ConsoleApi.GetConsoleMode(hStdOut, out uint outConsoleMode))
var hStdOut = PInvoke.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE);
CONSOLE_MODE outConsoleMode;
if (!PInvoke.GetConsoleMode(hStdOut, &outConsoleMode))
{
Logger.Instance.Log("Could not get console mode", LogLevel.Debug);
return false;
}

outConsoleMode |= Native.ConsoleApi.ENABLE_VIRTUAL_TERMINAL_PROCESSING;// | Native.ConsoleApi.DISABLE_NEWLINE_AUTO_RETURN;
if (!Native.ConsoleApi.SetConsoleMode(hStdOut, outConsoleMode))
outConsoleMode |= CONSOLE_MODE.ENABLE_VIRTUAL_TERMINAL_PROCESSING;// | CONSOLE_MODE.DISABLE_NEWLINE_AUTO_RETURN;
if (!PInvoke.SetConsoleMode(hStdOut, outConsoleMode))
{
Logger.Instance.Log("Could not enable virtual terminal processing", LogLevel.Error);
return false;
Expand All @@ -34,11 +41,11 @@ public static bool EnableVT()
return true;
}

internal static SetConsoleCtrlEventHandler IgnoreConsoleCancelKeyPress;
internal static PHANDLER_ROUTINE IgnoreConsoleCancelKeyPress;

private static bool IgnoreConsoleCancelKeyPressMethod(CtrlTypes ctrlType)
private static BOOL IgnoreConsoleCancelKeyPressMethod(uint ctrlType)
{
if (ctrlType.In(CtrlTypes.CTRL_C_EVENT, CtrlTypes.CTRL_BREAK_EVENT))
if (ctrlType == (uint)CtrlTypes.CTRL_C_EVENT || ctrlType == (uint)CtrlTypes.CTRL_BREAK_EVENT)
return true;

return false;
Expand Down
10 changes: 6 additions & 4 deletions src/gsudo/Helpers/ProcessFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using Windows.Win32;
using Windows.Win32.System.Console;
using static gsudo.Native.ProcessApi;
using static gsudo.Native.TokensApi;

Expand Down Expand Up @@ -319,9 +321,9 @@ private static SafeProcessHandle CreateProcessWithToken(IntPtr newToken, string
if (Console.IsErrorRedirected | Console.IsInputRedirected | Console.IsOutputRedirected)
{
startupInfo.dwFlags |= STARTF_USESTDHANDLES;
startupInfo.hStdOutput = ConsoleApi.GetStdHandle(ConsoleApi.STD_OUTPUT_HANDLE);
startupInfo.hStdInput = ConsoleApi.GetStdHandle(ConsoleApi.STD_INPUT_HANDLE);
startupInfo.hStdError = ConsoleApi.GetStdHandle(ConsoleApi.STD_ERROR_HANDLE);
startupInfo.hStdOutput = PInvoke.GetStdHandle(STD_HANDLE.STD_OUTPUT_HANDLE);
startupInfo.hStdInput = PInvoke.GetStdHandle(STD_HANDLE.STD_INPUT_HANDLE);
startupInfo.hStdError = PInvoke.GetStdHandle(STD_HANDLE.STD_ERROR_HANDLE);
}

PROCESS_INFORMATION processInformation;
Expand Down Expand Up @@ -360,7 +362,7 @@ internal static void CreateProcessForTokenReplacement(string lpApplicationName,
Logger.Instance.Log($"Creating target process: {lpApplicationName} {args}", LogLevel.Debug);
if (!ProcessApi.CreateProcess(null, command, ref pSec, ref tSec, false, dwCreationFlags, IntPtr.Zero, null, ref sInfoEx, out pInfo))
{
throw new Win32Exception((int)ConsoleApi.GetLastError());
throw new Win32Exception();
}

var currentProcessHandle = ProcessApi.GetCurrentProcess();
Expand Down
2 changes: 1 addition & 1 deletion src/gsudo/Helpers/ProcessHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ 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 SafeProcessHandle GetSafeProcessHandle(this Process p) => p.SafeHandle;

public static AutoResetEvent GetProcessWaitHandle(this SafeProcessHandle processHandle) =>
new AutoResetEvent(false)
Expand Down
48 changes: 0 additions & 48 deletions src/gsudo/Native/ConsoleApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,6 @@ namespace gsudo.Native
/// </summary>
static class ConsoleApi
{
internal const int STD_INPUT_HANDLE = -10;
internal const int STD_OUTPUT_HANDLE = -11;
internal const int STD_ERROR_HANDLE = -12;

internal const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
internal const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008;

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetStdHandle(int nStdHandle);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint mode);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool GetConsoleMode(IntPtr handle, out uint mode);

internal delegate bool ConsoleEventDelegate(CtrlTypes ctrlType);

internal enum CtrlTypes : uint
{
CTRL_C_EVENT = 0,
Expand All @@ -37,36 +19,6 @@ internal enum CtrlTypes : uint
CTRL_SHUTDOWN_EVENT
}

internal delegate bool SetConsoleCtrlEventHandler(CtrlTypes sig);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler callback, bool add);

[DllImport("kernel32.dll")]
public static extern uint GetLastError();

[DllImport("kernel32.dll")]
internal static extern bool SetConsoleCursorPosition(IntPtr hConsoleOutput, PseudoConsoleApi.COORD CursorPosition);


// send ctrl-c
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool AttachConsole(int dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool FreeConsole();
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
internal static extern bool AllocConsole();

// Enumerated type for the control messages sent to the handler routine

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern System.IntPtr GetCommandLine();


[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint GetConsoleProcessList(uint[] processList, uint processCount);

Expand Down
16 changes: 16 additions & 0 deletions src/gsudo/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Console
GetStdHandle
SetConsoleMode
GetConsoleMode

AttachConsole
FreeConsole
#AllocConsole

Check warning on line 8 in src/gsudo/NativeMethods.txt

View workflow job for this annotation

GitHub Actions / Test

Method, type or constant "#AllocConsole" not found. It contains unexpected characters, possibly including invisible characters, which can happen when copying and pasting from docs.microsoft.com among other places. Try deleting the line and retyping it.

// Ctr-C Handling
SetConsoleCtrlHandler
GenerateConsoleCtrlEvent

GetCommandLine
// GetConsoleProcessList => Unable to migrate for net46

13 changes: 7 additions & 6 deletions src/gsudo/ProcessHosts/AttachedConsoleHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Threading.Tasks;
using gsudo.Helpers;
using gsudo.Rpc;
using Windows.Win32;
using static gsudo.Native.ConsoleApi;

namespace gsudo.ProcessHosts
Expand All @@ -26,11 +27,11 @@ public async Task Start(Connection connection, ElevationRequest elevationRequest

try
{
Native.ConsoleApi.FreeConsole();
int pid = elevationRequest.ConsoleProcessId;
if (Native.ConsoleApi.AttachConsole(pid))
PInvoke.FreeConsole();
uint pid = elevationRequest.ConsoleProcessId;
if (PInvoke.AttachConsole(pid))
{
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);
PInvoke.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);

try
{
Expand Down Expand Up @@ -79,8 +80,8 @@ public async Task Start(Connection connection, ElevationRequest elevationRequest
}
finally
{
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false);
Native.ConsoleApi.FreeConsole();
PInvoke.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false);
PInvoke.FreeConsole();
await connection.FlushAndCloseAll().ConfigureAwait(false);
}
}
Expand Down
13 changes: 3 additions & 10 deletions src/gsudo/ProcessHosts/PipedProcessHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Windows.Win32;
using static gsudo.Native.ConsoleApi;

namespace gsudo.ProcessHosts
Expand All @@ -27,7 +28,7 @@ class PipedProcessHost : IProcessHost

public async Task Start(Connection connection, ElevationRequest request)
{
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);
PInvoke.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, true);

_connection = connection;
_request = request;
Expand Down Expand Up @@ -68,7 +69,7 @@ public async Task Start(Connection connection, ElevationRequest request)
}
finally
{
Native.ConsoleApi.SetConsoleCtrlHandler(HandleConsoleCancelKeyPress, false);
PInvoke.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false);
if (process != null && !process.HasExited)
{
process?.Terminate();
Expand All @@ -77,14 +78,6 @@ public async Task Start(Connection connection, ElevationRequest request)
}
}

private static bool HandleConsoleCancelKeyPress(CtrlTypes ctrlType)
{
if (ctrlType.In(CtrlTypes.CTRL_C_EVENT, CtrlTypes.CTRL_BREAK_EVENT))
return true;

return false;
}

private bool ShouldWait(StreamReader streamReader)
{
try
Expand Down
2 changes: 1 addition & 1 deletion src/gsudo/ProcessHosts/TokenSwitchHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ await connection.ControlStream
}
finally
{
Native.ConsoleApi.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false);
Windows.Win32.PInvoke.SetConsoleCtrlHandler(ConsoleHelper.IgnoreConsoleCancelKeyPress, false);
await connection.FlushAndCloseAll().ConfigureAwait(false);
}
}
Expand Down
Loading

0 comments on commit 7b78ac8

Please sign in to comment.