# C#提权到SYSTEM,以安全会话截屏和模拟键盘

1、使用ntrights.exe 添加SeAssignPrimaryTokenPrivilege权限

对应 “计算机配置” -> “Windows 设置” -> “安全设置” -> “本地策略” -> “用户权限分配”->”替换一个进程级令牌”

C#
public static void AddTokenPrivilege()
{
    if (OperatingSystem.IsWindows())
    {
        WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();
        Execute("cmd.exe", new string[] {
            $"ntrights +r SeAssignPrimaryTokenPrivilege -u {windowsIdentity.Name}"
        });
    }

}
public static string Execute(string fileName, string arg, string[] commands)
{
    Process proc = new Process();
    proc.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Join("./"));
    proc.StartInfo.CreateNoWindow = true;
    proc.StartInfo.FileName = fileName;
    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.RedirectStandardError = true;
    proc.StartInfo.RedirectStandardInput = true;
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.Arguments = arg;
    proc.StartInfo.Verb = "runas";
    proc.Start();

    if (commands.Length > 0)
    {
        for (int i = 0; i < commands.Length; i++)
        {
            proc.StandardInput.WriteLine(commands[i]);
        }
    }

    proc.StandardInput.AutoFlush = true;
    proc.StandardInput.WriteLine("exit");
    proc.StandardInput.Close();
    string output = proc.StandardOutput.ReadToEnd();
    string error = proc.StandardError.ReadToEnd();
    proc.WaitForExit();
    proc.Close();
    proc.Dispose();

    return output;
}

2、提权定义

将程序提权到SYSTEM权限

C#
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern nint GetCommandLine();
public static string GetCommandLineStr()
{
    nint commandLinePtr = GetCommandLineStr();
    return Marshal.PtrToStringAuto(commandLinePtr) ?? string.Empty;
}

[DataContract]
public enum WindowsSessionType
{
    Console = 1,
    RDP = 2
}
public enum WTS_INFO_CLASS
{
    WTSInitialProgram,
    WTSApplicationName,
    WTSWorkingDirectory,
    WTSOEMId,
    WTSSessionId,
    WTSUserName,
    WTSWinStationName,
    WTSDomainName,
    WTSConnectState,
    WTSClientBuildNumber,
    WTSClientName,
    WTSClientDirectory,
    WTSClientProductId,
    WTSClientHardwareId,
    WTSClientAddress,
    WTSClientDisplay,
    WTSClientProtocolType,
    WTSIdleTime,
    WTSLogonTime,
    WTSIncomingBytes,
    WTSOutgoingBytes,
    WTSIncomingFrames,
    WTSOutgoingFrames,
    WTSClientInfo,
    WTSSessionInfo
}
[StructLayout(LayoutKind.Sequential)]
public struct WTS_SESSION_INFO
{
    public uint SessionID;
    [MarshalAs(UnmanagedType.LPStr)]
    public string pWinStationName;
    public WTS_CONNECTSTATE_CLASS State;
}
public enum WTS_CONNECTSTATE_CLASS
{
    WTSActive,
    WTSConnected,
    WTSConnectQuery,
    WTSShadow,
    WTSDisconnected,
    WTSIdle,
    WTSListen,
    WTSReset,
    WTSDown,
    WTSInit
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO
{
    public int cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public int dwX;
    public int dwY;
    public int dwXSize;
    public int dwYSize;
    public int dwXCountChars;
    public int dwYCountChars;
    public int dwFillAttribute;
    public int dwFlags;
    public short wShowWindow;
    public short cbReserved2;
    public nint lpReserved2;
    public nint hStdInput;
    public nint hStdOutput;
    public nint hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
    public nint hProcess;
    public nint hThread;
    public int dwProcessId;
    public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int Length;
    public nint lpSecurityDescriptor;
    public bool bInheritHandle;
}
public enum SECURITY_IMPERSONATION_LEVEL : int
{
    SecurityAnonymous = 0,
    SecurityIdentification = 1,
    SecurityImpersonation = 2,
    SecurityDelegation = 3,
}
public enum TOKEN_TYPE : int
{
    TokenPrimary = 1,
    TokenImpersonation = 2
}
[DllImport("kernel32.dll")]
public static extern uint WTSGetActiveConsoleSessionId();
[DllImport("Wtsapi32.dll")]
public static extern bool WTSQuerySessionInformation(nint hServer, uint sessionId, WTS_INFO_CLASS wtsInfoClass, out nint ppBuffer, out uint pBytesReturned);
[DllImport("wtsapi32.dll", ExactSpelling = true, SetLastError = false)]
public static extern void WTSFreeMemory(nint memory);
[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern int WTSEnumerateSessions( nint hServer,int Reserved,int Version, ref nint ppSessionInfo, ref int pCount);
[DllImport("kernel32.dll")]
public static extern nint OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurity]
public static extern bool OpenProcessToken(nint ProcessHandle, int DesiredAccess, ref nint TokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool DuplicateTokenEx(
        nint hExistingToken,
        uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpTokenAttributes,
        SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
        TOKEN_TYPE TokenType,
        out nint phNewToken);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CreateProcessAsUser(
        nint hToken,
        string? lpApplicationName,
        string lpCommandLine,
        ref SECURITY_ATTRIBUTES lpProcessAttributes,
        ref SECURITY_ATTRIBUTES lpThreadAttributes,
        bool bInheritHandles,
        uint dwCreationFlags,
        nint lpEnvironment,
        string? lpCurrentDirectory,
        ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

public static string GetUsernameFromSessionId(uint sessionId)
{
    string username = string.Empty;

    if (WTSQuerySessionInformation(nint.Zero, sessionId,WTS_INFO_CLASS.WTSUserName, out var buffer, out var strLen) && strLen > 1)
    {
        username = Marshal.PtrToStringAnsi(buffer);
        WTSFreeMemory(buffer);
    }

    return username ?? string.Empty;
}
public static List<WindowsSession> GetActiveSessions()
{
    List<WindowsSession> sessions = new List<WindowsSession>();
    uint consoleSessionId = WTSGetActiveConsoleSessionId();
    sessions.Add(new WindowsSession()
    {
        Id = consoleSessionId,
        Type = WindowsSessionType.Console,
        Name = "Console",
        Username = GetUsernameFromSessionId(consoleSessionId)
    });

    nint ppSessionInfo = nint.Zero;
    int count = 0;
    int enumSessionResult = WTSAPI32.WTSEnumerateSessions(nint.Zero, 0, 1, ref ppSessionInfo, ref count);
    int dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
    nint current = ppSessionInfo;

    if (enumSessionResult != 0)
    {
        for (int i = 0; i < count; i++)
        {
            object wtsInfo = Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO));
            if (wtsInfo is null)
            {
                continue;
            }
            WTS_SESSION_INFO sessionInfo = (WTS_SESSION_INFO)wtsInfo;
            current += dataSize;
            if (sessionInfo.State == WTS_CONNECTSTATE_CLASS.WTSActive && sessionInfo.SessionID != consoleSessionId)
            {

                sessions.Add(new WindowsSession()
                {
                    Id = sessionInfo.SessionID,
                    Name = sessionInfo.pWinStationName,
                    Type = WindowsSessionType.RDP,
                    Username = GetUsernameFromSessionId(sessionInfo.SessionID)
                });
            }
        }
    }

    return sessions;
}

private static uint GetWinLogonPid(uint dwSessionId)
{
    uint winlogonPid = 0;
    Process[] processes = Process.GetProcessesByName("winlogon");
    foreach (Process p in processes)
    {
        if ((uint)p.SessionId == dwSessionId)
        {
            winlogonPid = (uint)p.Id;
        }
    }
    return winlogonPid;
}
private static uint GetDwSessionId(int targetSessionId,bool forceConsoleSession)
{
    uint dwSessionId = WTSGetActiveConsoleSessionId();
    if (forceConsoleSession == false)
    {
        List<WindowsSession> activeSessions = GetActiveSessions();
        if (activeSessions.Any(x => x.Id == targetSessionId))
        {
            dwSessionId = (uint)targetSessionId;
        }
        else
        {
            dwSessionId = activeSessions.Last().Id;
        }
    }
    return dwSessionId;
}
private static STARTUPINFO GetStartUpInfo(bool hiddenWindow, string desktopName, out uint dwCreationFlags)
{
    STARTUPINFO si = new STARTUPINFO();
    si.cb = Marshal.SizeOf(si);
    si.lpDesktop = @"winsta0\" + desktopName;
    if (hiddenWindow)
    {
        dwCreationFlags = 0x20| 0x00000400 | 0x08000000;
        si.dwFlags = 0x00000001;
        si.wShowWindow = 0;
    }
    else
    {
        dwCreationFlags = 0x20| 0x00000400 | 0x00000010;
    }
    return si;
}

public static bool CreateInteractiveSystemProcess(string commandLine, int targetSessionId, bool forceConsoleSession, string desktopName, bool hiddenWindow, out PROCESS_INFORMATION procInfo)
{
    nint hPToken = nint.Zero;
    procInfo = new PROCESS_INFORMATION();

    uint dwSessionId = GetDwSessionId(targetSessionId, forceConsoleSession);
    uint winlogonPid = GetWinLogonPid(dwSessionId);

    nint hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
    if (OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken) == false)
    {
        Kernel32.CloseHandle(hProcess);
        return false;
    }

    SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
    sa.Length = Marshal.SizeOf(sa);
    if (DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, out nint hUserTokenDup) == false)
    {
        Kernel32.CloseHandle(hProcess);
        Kernel32.CloseHandle(hPToken);
        return false;
    }

    STARTUPINFO si = GetStartUpInfo(hiddenWindow, desktopName,out uint dwCreationFlags);
    bool result = CreateProcessAsUser(hUserTokenDup, null, commandLine, ref sa, ref sa, false, dwCreationFlags, nint.Zero, null, ref si, out procInfo);

    Kernel32.CloseHandle(hProcess);
    Kernel32.CloseHandle(hPToken);
    Kernel32.CloseHandle(hUserTokenDup);

    return result;
}

3、使用提权

调用 RelaunchElevated() 将会以SYSTEM权限重启程序,并添加 –elevated 表示已提权,用以判断状态

C#
public static void RelaunchElevated()
{
    if (OperatingSystem.IsWindows() == false) return;

    try
    {
        AddTokenPrivilege();
    }
    catch
    {
    }
    try
    {
        string commandLine = GetCommandLine();
        bool result = CreateInteractiveSystemProcess($"{commandLine} --elevated", -1, false, "default", true, out PROCESS_INFORMATION procInfo);
        uint code = Kernel32.GetLastError();
        //提权成功则关闭本程序
        if (result)
        {
            Environment.Exit(0);
        }
    }
    catch
    {
    }
}