using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Ink_Canvas.Helpers
{
///
/// 通过 Winlogon 令牌模拟实现 UIAccess 提权重启。
/// 1. 找到当前会话中 winlogon.exe 的令牌,复制为模拟令牌;
/// 2. SetThreadToken 暂时模拟 winlogon(拥有 TCB 权限);
/// 3. 在自身令牌副本上 SetTokenInformation(TokenUIAccess, TRUE);
/// 4. RevertToSelf 后用 CreateProcessWithTokenW 启动新进程;
/// 5. 新进程具有 UIAccess 权限,可置顶于 UAC 提示之上。
///
public static class UIAccessHelper
{
#region Constants
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_IMPERSONATE = 0x0004;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const uint TOKEN_ADJUST_DEFAULT = 0x0080;
private const uint TOKEN_ADJUST_SESSIONID = 0x0100;
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const int SecurityAnonymous = 0;
private const int SecurityImpersonation = 2;
private const int TokenPrimary = 1;
private const int TokenImpersonation = 2;
// TOKEN_INFORMATION_CLASS
private const int TokenSessionId = 12;
private const int TokenElevationType = 18;
private const int TokenUIAccess = 26;
// TOKEN_ELEVATION_TYPE
private const int TokenElevationTypeDefault = 1;
private const int TokenElevationTypeFull = 2;
private const int TokenElevationTypeLimited = 3;
private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
private const uint TH32CS_SNAPPROCESS = 0x00000002;
private const uint LOGON_WITH_PROFILE = 0x00000001;
private const uint CREATE_NEW_CONSOLE = 0x00000010;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege";
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
#endregion
#region Structs
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privilege;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct PROCESSENTRY32W
{
public uint dwSize;
public uint cntUsage;
public uint th32ProcessID;
public IntPtr th32DefaultHeapID;
public uint th32ModuleID;
public uint cntThreads;
public uint th32ParentProcessID;
public int pcPriClassBase;
public uint dwFlags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szExeFile;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFOW
{
public uint cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public uint dwX, dwY, dwXSize, dwYSize;
public uint dwXCountChars, dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public ushort wShowWindow;
public ushort cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput, hStdOutput, hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
#endregion
#region P/Invoke
[DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
IntPtr lpTokenAttributes,
int ImpersonationLevel,
int TokenType,
out IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetTokenInformation(
IntPtr TokenHandle,
int TokenInformationClass,
IntPtr TokenInformation,
uint TokenInformationLength,
out uint ReturnLength);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetTokenInformation(
IntPtr TokenHandle,
int TokenInformationClass,
IntPtr TokenInformation,
uint TokenInformationLength);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetThreadToken(IntPtr Thread, IntPtr Token);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool RevertToSelf();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool Process32FirstW(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool Process32NextW(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool LookupPrivilegeValueW(string lpSystemName, string lpName, out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
uint BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CreateProcessWithTokenW(
IntPtr hToken,
uint dwLogonFlags,
string lpApplicationName,
StringBuilder lpCommandLine,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFOW lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void GetStartupInfoW(ref STARTUPINFOW lpStartupInfo);
#endregion
#region Public API
///
/// 检查当前进程是否已具有 UIAccess 标志。
///
public static bool HasUIAccess()
{
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hToken))
return false;
try
{
IntPtr buf = Marshal.AllocHGlobal(sizeof(uint));
try
{
Marshal.WriteInt32(buf, 0);
if (!GetTokenInformation(hToken, TokenUIAccess, buf, sizeof(uint), out _))
return false;
return Marshal.ReadInt32(buf) != 0;
}
finally
{
Marshal.FreeHGlobal(buf);
}
}
finally
{
CloseHandle(hToken);
}
}
///
/// 以 UIAccess 令牌重启自身。当前进程必须已经以管理员身份运行。
/// 成功时新进程已启动,调用方应立即退出当前进程。
///
/// 追加到新进程的额外命令行参数(例如 --skip-mutex-check)。
public static bool RestartWithUIAccess(string extraArgs = null)
{
try
{
if (HasUIAccess())
{
LogHelper.WriteLogToFile("UIAccess | 当前进程已具有 UIAccess,跳过重启");
return true;
}
if (!CreateUIAccessToken(out IntPtr uiaToken))
{
LogHelper.WriteLogToFile($"UIAccess | 创建 UIAccess 令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
return false;
}
try
{
return LaunchWithToken(uiaToken, extraArgs);
}
finally
{
CloseHandle(uiaToken);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"UIAccess | RestartWithUIAccess 异常: {ex}", LogHelper.LogType.Error);
return false;
}
}
///
/// 以普通用户权限(非提升)重启自身。
/// 通过获取 explorer.exe / ctfmon.exe 的非特权令牌,再用 CreateProcessWithTokenW 启动新进程,
/// 避免经由 explorer.exe 中转可能产生的 UAC 提示或丢失参数问题。
/// 成功时调用方应立即退出当前进程。
///
/// 追加到新进程的额外命令行参数。
public static bool RestartAsNormalUser(string extraArgs = null)
{
try
{
if (!GetCurrentProcessSessionId(out uint sessionId))
{
LogHelper.WriteLogToFile($"UIAccess | 获取当前会话 ID 失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
return false;
}
if (!GetUserPrimaryToken(sessionId, out IntPtr userToken))
{
LogHelper.WriteLogToFile($"UIAccess | 获取用户令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
return false;
}
try
{
return LaunchWithToken(userToken, extraArgs);
}
finally
{
CloseHandle(userToken);
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"UIAccess | RestartAsNormalUser 异常: {ex}", LogHelper.LogType.Error);
return false;
}
}
#endregion
#region Token Manipulation
private static bool CreateUIAccessToken(out IntPtr uiaToken)
{
uiaToken = IntPtr.Zero;
// 1. 获取当前进程的 session id
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hSelfQuery))
{
LogHelper.WriteLogToFile($"UIAccess | OpenProcessToken(query) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
return false;
}
uint sessionId;
try
{
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
try
{
if (!GetTokenInformation(hSelfQuery, TokenSessionId, sesBuf, sizeof(uint), out _))
{
LogHelper.WriteLogToFile($"UIAccess | GetTokenInformation(SessionId) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
return false;
}
sessionId = (uint)Marshal.ReadInt32(sesBuf);
}
finally { Marshal.FreeHGlobal(sesBuf); }
}
finally { CloseHandle(hSelfQuery); }
// 2. 找到同一会话的 winlogon 模拟令牌
if (!GetWinlogonImpersonationToken(sessionId, out IntPtr winlogonToken))
{
LogHelper.WriteLogToFile("UIAccess | 未能获取 winlogon 模拟令牌(需要管理员权限)", LogHelper.LogType.Error);
return false;
}
try
{
// 3. 模拟 winlogon
if (!SetThreadToken(IntPtr.Zero, winlogonToken))
{
LogHelper.WriteLogToFile($"UIAccess | SetThreadToken(winlogon) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
return false;
}
try
{
// 4. 复制自身令牌为主令牌
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, out IntPtr hSelfDup))
{
LogHelper.WriteLogToFile($"UIAccess | OpenProcessToken(dup) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
return false;
}
IntPtr dupToken;
try
{
bool ok = DuplicateTokenEx(
hSelfDup,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
IntPtr.Zero,
SecurityAnonymous,
TokenPrimary,
out dupToken);
if (!ok)
{
LogHelper.WriteLogToFile($"UIAccess | DuplicateTokenEx 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
return false;
}
}
finally { CloseHandle(hSelfDup); }
// 5. 在副本上设置 UIAccess = TRUE
IntPtr uiBuf = Marshal.AllocHGlobal(sizeof(uint));
try
{
Marshal.WriteInt32(uiBuf, 1);
if (!SetTokenInformation(dupToken, TokenUIAccess, uiBuf, sizeof(uint)))
{
int err = Marshal.GetLastWin32Error();
LogHelper.WriteLogToFile($"UIAccess | SetTokenInformation(UIAccess) 失败: {err}", LogHelper.LogType.Error);
CloseHandle(dupToken);
return false;
}
}
finally { Marshal.FreeHGlobal(uiBuf); }
uiaToken = dupToken;
return true;
}
finally
{
RevertToSelf();
}
}
finally
{
CloseHandle(winlogonToken);
}
}
private static bool GetWinlogonImpersonationToken(uint sessionId, out IntPtr winlogonToken)
{
winlogonToken = IntPtr.Zero;
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero)
{
LogHelper.WriteLogToFile($"UIAccess | CreateToolhelp32Snapshot 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
return false;
}
try
{
var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
bool more = Process32FirstW(snapshot, ref pe);
while (more)
{
if (string.Equals(pe.szExeFile, "winlogon.exe", StringComparison.OrdinalIgnoreCase))
{
if (TryDuplicateWinlogonToken(pe.th32ProcessID, sessionId, out winlogonToken))
return true;
}
more = Process32NextW(snapshot, ref pe);
}
}
finally { CloseHandle(snapshot); }
return false;
}
private static bool TryDuplicateWinlogonToken(uint pid, uint sessionId, out IntPtr dupToken)
{
dupToken = IntPtr.Zero;
IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
if (hProc == IntPtr.Zero) return false;
try
{
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
return false;
try
{
// 检查 session id 匹配
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
try
{
if (!GetTokenInformation(hToken, TokenSessionId, sesBuf, sizeof(uint), out _))
return false;
if ((uint)Marshal.ReadInt32(sesBuf) != sessionId)
return false;
}
finally { Marshal.FreeHGlobal(sesBuf); }
if (!DuplicateTokenEx(
hToken,
TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE,
IntPtr.Zero,
SecurityImpersonation,
TokenImpersonation,
out dupToken))
{
return false;
}
// 启用 SeAssignPrimaryTokenPrivilege(Inkeys 行为)
var tkp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privilege = new LUID_AND_ATTRIBUTES { Attributes = SE_PRIVILEGE_ENABLED }
};
if (LookupPrivilegeValueW(null, SE_ASSIGNPRIMARYTOKEN_NAME, out tkp.Privilege.Luid))
{
AdjustTokenPrivileges(dupToken, false, ref tkp, (uint)Marshal.SizeOf(tkp), IntPtr.Zero, IntPtr.Zero);
}
return true;
}
finally { CloseHandle(hToken); }
}
finally { CloseHandle(hProc); }
}
private static bool GetCurrentProcessSessionId(out uint sessionId)
{
sessionId = 0;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hSelfQuery))
return false;
try
{
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
try
{
if (!GetTokenInformation(hSelfQuery, TokenSessionId, sesBuf, sizeof(uint), out _))
return false;
sessionId = (uint)Marshal.ReadInt32(sesBuf);
return true;
}
finally { Marshal.FreeHGlobal(sesBuf); }
}
finally { CloseHandle(hSelfQuery); }
}
///
/// 从 explorer.exe / ctfmon.exe 取得普通用户(非提升)令牌的主令牌副本,用于降权启动。
/// 仅当当前进程为管理员时才能成功。
///
private static bool GetUserPrimaryToken(uint sessionId, out IntPtr userToken)
{
userToken = IntPtr.Zero;
string[] candidates = { "explorer.exe", "ctfmon.exe" };
foreach (var name in candidates)
{
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero) continue;
try
{
var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
bool more = Process32FirstW(snapshot, ref pe);
while (more)
{
if (string.Equals(pe.szExeFile, name, StringComparison.OrdinalIgnoreCase))
{
if (TryDuplicateUserPrimaryToken(pe.th32ProcessID, sessionId, out userToken))
{
LogHelper.WriteLogToFile($"UIAccess | 已从 {name} (PID={pe.th32ProcessID}, Session={sessionId}) 取得用户令牌");
return true;
}
}
more = Process32NextW(snapshot, ref pe);
}
}
finally { CloseHandle(snapshot); }
}
return false;
}
private static bool TryDuplicateUserPrimaryToken(uint pid, uint sessionId, out IntPtr dupToken)
{
dupToken = IntPtr.Zero;
IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
if (hProc == IntPtr.Zero) return false;
try
{
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
return false;
try
{
// 会话隔离:拒绝来自其他登录会话(RDP / 终端服务 / 快速用户切换)的令牌,
// 否则降权后进程会落到错误用户的桌面上下文中。
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
try
{
if (!GetTokenInformation(hToken, TokenSessionId, sesBuf, sizeof(uint), out _))
return false;
if ((uint)Marshal.ReadInt32(sesBuf) != sessionId)
return false;
}
finally { Marshal.FreeHGlobal(sesBuf); }
// 仅接受非提升令牌(否则降权失败)
IntPtr elevBuf = Marshal.AllocHGlobal(sizeof(int));
try
{
if (!GetTokenInformation(hToken, TokenElevationType, elevBuf, sizeof(int), out _))
return false;
int elev = Marshal.ReadInt32(elevBuf);
if (elev == TokenElevationTypeFull)
return false; // 该进程是提升令牌,跳过
}
finally { Marshal.FreeHGlobal(elevBuf); }
return DuplicateTokenEx(
hToken,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
IntPtr.Zero,
SecurityAnonymous,
TokenPrimary,
out dupToken);
}
finally { CloseHandle(hToken); }
}
finally { CloseHandle(hProc); }
}
#endregion
#region Process Launch
private static bool LaunchWithToken(IntPtr token, string extraArgs)
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
string workDir = System.IO.Path.GetDirectoryName(exePath);
// 重建命令行:保留原始参数,追加 --skip-mutex-check 防止单实例阻塞
var cmdBuilder = new StringBuilder(32768);
cmdBuilder.Append('"').Append(exePath).Append('"');
string[] args = Environment.GetCommandLineArgs();
for (int i = 1; i < args.Length; i++)
{
cmdBuilder.Append(' ');
AppendQuoted(cmdBuilder, args[i]);
}
if (!string.IsNullOrEmpty(extraArgs))
cmdBuilder.Append(' ').Append(extraArgs);
// 防止单实例 Mutex 阻塞新进程
if (Array.IndexOf(args, "--skip-mutex-check") < 0
&& (extraArgs == null || extraArgs.IndexOf("--skip-mutex-check", StringComparison.Ordinal) < 0))
{
cmdBuilder.Append(" --skip-mutex-check");
}
var si = new STARTUPINFOW { cb = (uint)Marshal.SizeOf(typeof(STARTUPINFOW)) };
GetStartupInfoW(ref si);
bool ok = CreateProcessWithTokenW(
token,
LOGON_WITH_PROFILE,
null,
cmdBuilder,
CREATE_UNICODE_ENVIRONMENT,
IntPtr.Zero,
workDir,
ref si,
out PROCESS_INFORMATION pi);
if (!ok)
{
int err = Marshal.GetLastWin32Error();
LogHelper.WriteLogToFile($"UIAccess | CreateProcessWithTokenW 失败: {err}", LogHelper.LogType.Error);
return false;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
LogHelper.WriteLogToFile($"UIAccess | 已使用 UIAccess 令牌启动新进程 (PID={pi.dwProcessId})");
return true;
}
private static void AppendQuoted(StringBuilder sb, string arg)
{
if (arg == null) { sb.Append("\"\""); return; }
bool needQuote = arg.Length == 0 || arg.IndexOfAny(new[] { ' ', '\t', '"' }) >= 0;
if (!needQuote) { sb.Append(arg); return; }
sb.Append('"');
int backslashes = 0;
foreach (char c in arg)
{
if (c == '\\') { backslashes++; continue; }
if (c == '"')
{
sb.Append('\\', backslashes * 2 + 1);
sb.Append('"');
}
else
{
sb.Append('\\', backslashes);
sb.Append(c);
}
backslashes = 0;
}
sb.Append('\\', backslashes * 2);
sb.Append('"');
}
#endregion
}
}