315 lines
11 KiB
PowerShell
315 lines
11 KiB
PowerShell
|
# this is a very stripped-down C# port of NSudo:
|
||
|
# https://github.com/M2Team/NSudo
|
||
|
# you should already be running this script as an administrator.
|
||
|
|
||
|
param([string]$command="cmd")
|
||
|
|
||
|
function Execute-Elevated {
|
||
|
param([string]$command="cmd")
|
||
|
|
||
|
$Definition = @"
|
||
|
using System;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using System.Diagnostics;
|
||
|
|
||
|
public class Dummy {
|
||
|
const int CREATE_NO_WINDOW = 0x8000000;
|
||
|
const int CREATE_UNICODE_ENVIRONMENT = 0x400;
|
||
|
const int MAXIMUM_ALLOWED = 0x2000000;
|
||
|
const int PROCESS_QUERY_INFORMATION = 0x400;
|
||
|
const int SE_PRIVILEGE_ENABLED = 0x2;
|
||
|
const int SecurityImpersonation = 2;
|
||
|
const int TOKEN_QUERY = 0x8;
|
||
|
const int TokenPrimary = 1;
|
||
|
const int TokenImpersonation = 2;
|
||
|
const int TokenUser = 1;
|
||
|
|
||
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
public struct SID_AND_ATTRIBUTES {
|
||
|
public IntPtr Sid;
|
||
|
public int Attributes;
|
||
|
}
|
||
|
|
||
|
public struct TOKEN_USER {
|
||
|
public SID_AND_ATTRIBUTES User;
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
public struct LUID {
|
||
|
public UInt32 LowPart;
|
||
|
public Int32 HighPart;
|
||
|
}
|
||
|
|
||
|
// hack to make a simple TOKEN_PRIVILEGES struct when Count=1.
|
||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||
|
public struct TokenPrivilegeSingle {
|
||
|
public int Count;
|
||
|
public LUID Luid;
|
||
|
public uint Attr;
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
||
|
public struct STARTUPINFOW {
|
||
|
public int cb;
|
||
|
public string lpReserved;
|
||
|
public string lpDesktop;
|
||
|
public string lpTitle;
|
||
|
public uint dwX;
|
||
|
public uint dwY;
|
||
|
public uint dwXSize;
|
||
|
public uint dwYSize;
|
||
|
public uint dwXCountChars;
|
||
|
public uint dwYCountChars;
|
||
|
public uint dwFillAttribute;
|
||
|
public uint dwFlags;
|
||
|
public ushort wShowWindow;
|
||
|
public ushort cbReserved2;
|
||
|
public IntPtr lpReserved2;
|
||
|
public IntPtr hStdInput;
|
||
|
public IntPtr hStdOutput;
|
||
|
public IntPtr hStdError;
|
||
|
}
|
||
|
|
||
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
internal struct PROCESS_INFORMATION {
|
||
|
public IntPtr hProcess;
|
||
|
public IntPtr hThread;
|
||
|
public int dwProcessId;
|
||
|
public int dwThreadId;
|
||
|
}
|
||
|
|
||
|
[DllImport("kernel32.dll")]
|
||
|
static extern IntPtr LocalFree(
|
||
|
IntPtr hMem);
|
||
|
|
||
|
[DllImport("kernel32.dll", SetLastError=true)]
|
||
|
static extern bool CloseHandle(
|
||
|
IntPtr hHandle);
|
||
|
|
||
|
[DllImport("userenv.dll", SetLastError=true)]
|
||
|
static extern bool CreateEnvironmentBlock(
|
||
|
out IntPtr lpEnvironment,
|
||
|
IntPtr hToken, bool bInherit);
|
||
|
|
||
|
[DllImport("userenv.dll", SetLastError=true)]
|
||
|
static extern bool DestroyEnvironmentBlock(
|
||
|
IntPtr lpEnvironment);
|
||
|
|
||
|
[DllImport("advapi32", CharSet=CharSet.Auto, SetLastError=true)]
|
||
|
static extern bool ConvertSidToStringSid(
|
||
|
IntPtr pSID,
|
||
|
out IntPtr ptrSid);
|
||
|
|
||
|
[DllImport("kernel32.dll", SetLastError=true)]
|
||
|
static extern IntPtr OpenProcess(
|
||
|
int dwDesiredAccess,
|
||
|
bool bInheritHandle,
|
||
|
long dwProcessId);
|
||
|
|
||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||
|
static extern bool OpenProcessToken(
|
||
|
IntPtr h,
|
||
|
int acc,
|
||
|
out IntPtr phtok);
|
||
|
|
||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||
|
private static extern bool SetThreadToken(
|
||
|
IntPtr pHandle,
|
||
|
IntPtr hToken);
|
||
|
|
||
|
[DllImport("advapi32.dll", SetLastError=true)]
|
||
|
static extern bool GetTokenInformation(
|
||
|
IntPtr TokenHandle,
|
||
|
int TokenInformationClass,
|
||
|
IntPtr TokenInformation,
|
||
|
int TokenInformationLength,
|
||
|
out int ReturnLength);
|
||
|
|
||
|
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)]
|
||
|
static extern bool DuplicateTokenEx(
|
||
|
IntPtr hExistingToken,
|
||
|
uint dwDesiredAccess,
|
||
|
IntPtr lpTokenAttributes,
|
||
|
int ImpersonationLevel,
|
||
|
int TokenType,
|
||
|
out IntPtr phNewToken);
|
||
|
|
||
|
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
||
|
static extern bool CreateProcessAsUserW(
|
||
|
IntPtr hToken,
|
||
|
string lpApplicationName,
|
||
|
string lpCommandLine,
|
||
|
IntPtr lpProcessAttributes,
|
||
|
IntPtr lpThreadAttributes,
|
||
|
bool bInheritHandles,
|
||
|
uint dwCreationFlags,
|
||
|
IntPtr lpEnvironment,
|
||
|
string lpCurrentDirectory,
|
||
|
ref STARTUPINFOW lpStartupInfo,
|
||
|
out PROCESS_INFORMATION lpProcessInformation);
|
||
|
|
||
|
[DllImport("advapi32.dll", SetLastError = true)]
|
||
|
static extern bool AdjustTokenPrivileges(
|
||
|
IntPtr TokenHandle,
|
||
|
bool DisableAllPrivileges,
|
||
|
ref TokenPrivilegeSingle NewState,
|
||
|
uint Zero,
|
||
|
IntPtr Null1,
|
||
|
IntPtr Null2);
|
||
|
|
||
|
public static string ConvertSidToStringSid(IntPtr sid) {
|
||
|
IntPtr pstr = IntPtr.Zero;
|
||
|
bool ok = ConvertSidToStringSid(sid, out pstr);
|
||
|
if (!ok) {
|
||
|
LocalFree(pstr);
|
||
|
throw new Exception("ConvertSidToStringSid: not ok");
|
||
|
}
|
||
|
string sidstr = Marshal.PtrToStringAuto(pstr);
|
||
|
LocalFree(pstr);
|
||
|
return sidstr;
|
||
|
}
|
||
|
|
||
|
public static bool HasPrivileges(int pid) {
|
||
|
bool ok = true;
|
||
|
IntPtr hProc = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
|
||
|
if (hProc == IntPtr.Zero) throw new Exception("OpenProcess: null");
|
||
|
IntPtr hToken = IntPtr.Zero;
|
||
|
ok = OpenProcessToken(hProc, TOKEN_QUERY, out hToken);
|
||
|
if (!ok || hToken == IntPtr.Zero) throw new Exception("OpenProcessToken: not ok or null");
|
||
|
|
||
|
int tiLength = -1;
|
||
|
GetTokenInformation(hToken, TokenUser, IntPtr.Zero, 0, out tiLength);
|
||
|
IntPtr ti = Marshal.AllocHGlobal(tiLength);
|
||
|
ok = GetTokenInformation(hToken, TokenUser, ti, tiLength, out tiLength);
|
||
|
if (!ok) {
|
||
|
Marshal.FreeHGlobal(ti);
|
||
|
throw new Exception("GetTokenInformation: not ok");
|
||
|
}
|
||
|
|
||
|
TOKEN_USER tokenUser = (TOKEN_USER)Marshal.PtrToStructure(ti, typeof(TOKEN_USER));
|
||
|
string sidstr = ConvertSidToStringSid(tokenUser.User.Sid);
|
||
|
|
||
|
return sidstr == "S-1-5-18"; // described as "Local System"
|
||
|
}
|
||
|
|
||
|
public static int FindSystemPid(string processName) {
|
||
|
Process currentProc = Process.GetCurrentProcess();
|
||
|
Process[] processes = Process.GetProcessesByName(processName);
|
||
|
int pid = -1;
|
||
|
foreach (Process proc in processes) {
|
||
|
if (currentProc.SessionId == proc.SessionId && HasPrivileges(proc.Id)) {
|
||
|
pid = proc.Id;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (pid == -1) throw new Exception("FindSystemPid: couldn't find " + processName);
|
||
|
return pid;
|
||
|
}
|
||
|
|
||
|
public static IntPtr DuplicateProcessToken(int pid, int impLevel, int tokenType) {
|
||
|
// desiredAccess is always MAXIMUM_ALLOWED
|
||
|
// lpTokenAttributes is always null (IntPtr.Zero)
|
||
|
bool ok = true;
|
||
|
IntPtr hProc = OpenProcess(MAXIMUM_ALLOWED, false, pid);
|
||
|
if (hProc == IntPtr.Zero) throw new Exception("OpenProcess: null");
|
||
|
|
||
|
IntPtr hToken = IntPtr.Zero;
|
||
|
ok = OpenProcessToken(hProc, MAXIMUM_ALLOWED, out hToken);
|
||
|
if (!ok || hToken == IntPtr.Zero) throw new Exception("OpenProcessToken: not ok or null");
|
||
|
|
||
|
IntPtr phToken = IntPtr.Zero;
|
||
|
ok = DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, IntPtr.Zero,
|
||
|
impLevel, tokenType,
|
||
|
out phToken);
|
||
|
if (!ok || phToken == IntPtr.Zero) throw new Exception("DuplicateTokenEx: not ok or null");
|
||
|
|
||
|
return phToken;
|
||
|
}
|
||
|
|
||
|
public static void CreateProcessW(IntPtr hToken, IntPtr environment, string directory,
|
||
|
string comSpec, string command) {
|
||
|
bool ok = true;
|
||
|
|
||
|
STARTUPINFOW startupInfo = new STARTUPINFOW();
|
||
|
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
|
||
|
|
||
|
startupInfo.cb = Marshal.SizeOf(startupInfo);
|
||
|
startupInfo.lpDesktop = "WinSta0\\Default";
|
||
|
|
||
|
ok = CreateProcessAsUserW(hToken, comSpec, command,
|
||
|
IntPtr.Zero, IntPtr.Zero, false,
|
||
|
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT,
|
||
|
environment, directory,
|
||
|
ref startupInfo, out processInfo);
|
||
|
|
||
|
if (!ok) {
|
||
|
int err = Marshal.GetLastWin32Error();
|
||
|
CloseHandle(processInfo.hProcess);
|
||
|
CloseHandle(processInfo.hThread);
|
||
|
throw new Exception("CreateProcessAsUser failure: " + err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static IntPtr CreateEnvironment(IntPtr hToken) {
|
||
|
bool ok = true;
|
||
|
IntPtr environment;
|
||
|
ok = CreateEnvironmentBlock(out environment, hToken, true);
|
||
|
if (!ok || environment == IntPtr.Zero) throw new Exception("CreateEnvironmentBlock: not ok or null");
|
||
|
return environment;
|
||
|
}
|
||
|
|
||
|
public static void SetTokenAllPrivileges(IntPtr hToken, bool enable) {
|
||
|
// turns out the only one we need is 3: SE_ASSIGNPRIMARYTOKEN_PRIVILEGE
|
||
|
bool ok = true;
|
||
|
|
||
|
TokenPrivilegeSingle tp = new TokenPrivilegeSingle();
|
||
|
tp.Count = 1;
|
||
|
tp.Attr = (uint)(enable ? SE_PRIVILEGE_ENABLED : 0);
|
||
|
tp.Luid.LowPart = 3;
|
||
|
|
||
|
ok = AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
|
||
|
if (!ok) throw new Exception("AdjustTokenPrivileges: not ok");
|
||
|
|
||
|
int err = Marshal.GetLastWin32Error();
|
||
|
if (err != 0) throw new Exception("AdjustTokenPrivileges failure: " + err);
|
||
|
}
|
||
|
|
||
|
public static void Execute(string comSpec, string command) {
|
||
|
bool ok = true;
|
||
|
|
||
|
IntPtr hTokenSelf;
|
||
|
|
||
|
ok = OpenProcessToken((IntPtr)(-1), MAXIMUM_ALLOWED, out hTokenSelf);
|
||
|
if (!ok || hTokenSelf == IntPtr.Zero) throw new Exception("OpenProcessToken: not ok or null");
|
||
|
|
||
|
IntPtr environment = CreateEnvironment(hTokenSelf);
|
||
|
|
||
|
int pid = FindSystemPid("winlogon");
|
||
|
Console.WriteLine("winlogon PID: " + pid);
|
||
|
|
||
|
IntPtr hTokenSystemImpersonate = DuplicateProcessToken(pid, SecurityImpersonation, TokenImpersonation);
|
||
|
|
||
|
SetTokenAllPrivileges(hTokenSystemImpersonate, true);
|
||
|
ok = SetThreadToken(IntPtr.Zero, hTokenSystemImpersonate);
|
||
|
if (!ok) throw new Exception("SetThreadToken: not ok");
|
||
|
|
||
|
IntPtr hTokenSystemPrimary = DuplicateProcessToken(pid, SecurityImpersonation, TokenPrimary);
|
||
|
|
||
|
string fullCommand = "/c start \"" + comSpec + "\" " + command;
|
||
|
Console.WriteLine(comSpec + " " + fullCommand);
|
||
|
|
||
|
CreateProcessW(hTokenSystemPrimary, environment, null, comSpec, fullCommand);
|
||
|
|
||
|
// TODO: always clean up regardless of exceptions.
|
||
|
DestroyEnvironmentBlock(environment);
|
||
|
}
|
||
|
}
|
||
|
"@
|
||
|
|
||
|
$type = Add-Type $definition -PassThru
|
||
|
$type[0]::Execute((get-item env:"ComSpec").Value, $command)
|
||
|
}
|
||
|
|
||
|
Execute-Elevated $command
|
||
|
exit
|