using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Pipes; using System.Text.Json; using System.Threading; using PCL.Core.App.IoC; using PCL.Core.IO; using PCL.Core.Logging; using PCL.Core.Utils.OS; using PCL.Core.Utils; namespace PCL.Core.App.Essentials; public delegate string? PromoteOperationFunction(string? arg); /// /// 标记一个方法,使其能够被提权进程调用,方法签名需符合 。 /// /// 提权操作名 [DependencyCollector("promote", AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)] public sealed class PromoteOperationAttribute(string name) : Attribute; [LifecycleService(LifecycleState.BeforeLoading, Priority = -10)] [LifecycleScope("promote", "提权服务", false)] public sealed partial class PromoteService { private static Process? _promoteProcess; private static NamedPipeServerStream? _promotePipeServer; private static readonly ConcurrentQueue _PendingOperations = []; private readonly record struct PromoteOperation(string Command, Action? Callback, bool DetailLog); /// /// 提权进程是否正在运行。 /// public static bool IsPromoteProcessRunning => _promoteProcess is not null; /// /// 当前进程是否是提权进程。 /// public static bool IsCurrentProcessPromoted { get; private set; } private static string _GetPromotePipeName(int processId) => $"PCLCE_PM@{processId}"; private static readonly Dictionary _OperationFunctions = new(); /// /// 添加提权操作,仅在提权进程中有效。 /// /// 操作名 /// 操作实现,接收参数并返回结果,返回值会被自动压缩为单行 /// 是否添加成功,若在主进程中调用或已存在相同操作名,则为 false public static bool AddOperationFunction(string name, PromoteOperationFunction operation) { return IsCurrentProcessPromoted && _OperationFunctions.TryAdd(name, operation); } /// /// 添加自动将参数 JSON 反序列化的提权操作,仅在提权进程中有效。 /// /// 操作名 /// 操作实现,接收反序列化的并返回结果,返回值会被自动压缩为单行 /// 反序列化的目标类型 /// 是否添加成功,若在主进程中调用或已存在相同操作名,则为 false public static bool AddJsonOperationFunction(string name, Func operation) { return AddOperationFunction(name, arg => { if (arg is null) return OperationErrEmpty; var obj = JsonSerializer.Deserialize(arg, JsonCompat.SerializerOptions); return operation(obj); }); } private const string OperationErrNotFound = "ERR_OPERATION_NOT_FOUND"; private const string OperationErrInvalidArgument = "ERR_ILLEGAL_ARGUMENT"; private const string OperationErrExceptionThrown = "ERR_UNHANDLED_EXCEPTION"; private const string OperationErrEmpty = "ERR_EMPTY"; /// /// 提权进程接收到操作请求时触发的事件,接收一个字符串作为操作命令并返回一个字符串作为结果。
/// 注意:如果你不知道这是做什么的,请勿覆盖默认实现。请使用 。 ///
public static Func Operate { private get => field ??= command => { var split = command.Split([' '], 2); _OperationFunctions.TryGetValue(split[0], out var operation); if (operation is null) return OperationErrNotFound; try { return operation(split.Length > 1 ? split[1] : null) ?? OperationErrEmpty; } catch (Exception ex) { Context.Warn("操作出错", ex); return OperationErrExceptionThrown; } }; set; } = null!; private static string _ShortenString(string str) { #if TRACE const int maxLength = 40; #else const int maxLength = 15; #endif if (str.Length <= maxLength) return str; return str[..maxLength] + "..."; } // 提权进程: 连接管道开始通信 private static void _PerformAsPromoteProcess(string pid) { Context.Info("正在连接提权通信管道"); var process = Process.GetProcessById(int.Parse(pid)); // 验证来源 var mainProcessPath = Path.GetFullPath(process.MainModule!.FileName); if (!string.Equals(mainProcessPath, Basics.ExecutablePath, StringComparison.OrdinalIgnoreCase)) { Context.Error("来源验证失败,正在退出"); return; } // 连接管道 var pipeName = _GetPromotePipeName(process.Id); var pipe = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut); pipe.Connect(10000); Context.Info("已连接,开始通信"); var reader = new StreamReader(pipe); var writer = new StreamWriter(pipe); while (true) { var command = reader.ReadLine(); if (string.IsNullOrEmpty(command)) { Context.Info("管道已关闭,正在退出"); break; } Context.Debug($"正在执行: {_ShortenString(command)}"); var result = Operate(command) ?? OperationErrEmpty; Context.Trace($"返回结果: {_ShortenString(result)}"); writer.WriteLine(result.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ')); writer.Flush(); Context.Trace("返回成功"); } } private static readonly AutoResetEvent _ActivateEvent = new(false); // 主进程: 管道连接回调 private static bool _PromotePipeCallback(StreamReader reader, StreamWriter writer, Process? client) { while (IsPromoteProcessRunning) { if (!_PendingOperations.TryDequeue(out var operation)) { _ActivateEvent.WaitOne(); continue; } var command = operation.Command.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' '); var commandLog = operation.DetailLog ? command : _ShortenString(command); Context.Debug($"正在执行: {commandLog}"); writer.WriteLine(command); writer.Flush(); var result = reader.ReadLine(); if (result is null) { Context.Warn("管道输入流已结束"); break; } var resultLog = operation.DetailLog ? result : _ShortenString(result); Context.Trace($"执行结果: {resultLog}"); if (result == OperationErrEmpty) result = null; operation.Callback?.Invoke(result); } return false; } // 主进程: 初始化提权后台服务 private static bool _StartPromoteProcess() { // 启动提权进程 _promoteProcess = ProcessInterop.Start( Basics.ExecutablePath, $"promote {Basics.CurrentProcessId}", true); if (_promoteProcess is null) { Context.Warn("提权进程启动失败"); return false; } _promoteProcess.Exited += (_, _) => _promoteProcess = null; // 启动提权通信管道服务端 _promotePipeServer ??= PipeComm.StartPipeServer( "Promote", _GetPromotePipeName(Basics.CurrentProcessId), _PromotePipeCallback, () => _promotePipeServer = null, true, [_promoteProcess.Id]); return true; } /// /// 向等待区添加操作。 /// /// 操作命令 /// 结果返回后的回调 /// 指定是否打印详细日志,若为 false,则日志仅保留前 40 或 15 字符(取决于是否为调试构建) public static void Append(string command, Action? callback = null, bool detailLog = true) { _PendingOperations.Enqueue(new PromoteOperation(command, callback, detailLog)); } /// /// 尝试启动提权进程并开始执行操作。 /// /// 是否成功开始执行,若提权进程启动失败则为 false public static bool Activate() { if (!IsPromoteProcessRunning && !_StartPromoteProcess()) { _PendingOperations.Clear(); return false; } _ActivateEvent.Set(); return true; } private static readonly Dictionary _RunningProcesses = new(); // name: kill // arg: process-id [timeout] // return: kill result (false over timeout) [PromoteOperation("kill")] public static string? KillProcess(string? arg) { if (arg is null) return OperationErrInvalidArgument; var split = arg.Split(' '); if (!_RunningProcesses.TryGetValue(split[0], out var process)) return null; process.Kill(); if (split.Length > 1) { int.TryParse(split[1], out var timeout); return process.WaitForExit(timeout).ToString(); } process.WaitForExit(); return true.ToString(); } // name: start // arg: path\to\executable[.] ; arguments // return: process id [PromoteOperation("start")] public static string? StartProcess(string? arg) { if (arg is null) return OperationErrInvalidArgument; var split = arg.Split([" ; "], 2, StringSplitOptions.RemoveEmptyEntries); var createNoWindow = false; if (split[0].EndsWith('.')) { split[0] = split[0][..^1]; createNoWindow = true; } var psi = new ProcessStartInfo(split[0]); if (createNoWindow) { psi.CreateNoWindow = true; psi.UseShellExecute = false; psi.RedirectStandardInput = true; psi.RedirectStandardOutput = true; psi.RedirectStandardError = true; } if (split.Length > 1) psi.Arguments = split[1]; return _StartProcessWithInfo(psi); } // name: start-json // arg: {...} // return: process id private static string? _StartProcessWithInfo(ProcessStartInfo? info) { if (info is null) return OperationErrInvalidArgument; var process = Process.Start(info); if (process is null) return null; var id = process.Id.ToString(); process.Exited += (_, _) => _RunningProcesses.Remove(id); _RunningProcesses[id] = process; return id; } [DependencyInjectionPoint("promote", false)] private static void _CollectOperationFunction(PromoteOperationFunction operation, string name) => AddOperationFunction(name, operation); [LifecycleStart] private static void _Start() { var args = Basics.CommandLineArguments; if (args is ["promote", _]) { Context.Info("当前进程为提权进程"); IsCurrentProcessPromoted = true; // 预定义操作 Context.Info("正在加载提权操作"); _CollectOperationFunction_InvokeInjection_Promote(); AddJsonOperationFunction("start-json", _StartProcessWithInfo); // 结束生命周期管理,启动提权操作线程 Lifecycle.PendingLogFileName = "LastPending_Promote.log"; LogWrapper.OnLog += (level, msg, module, ex) => Context.CustomLog($"[{module}] {msg}", ex, level); Context.Info("已接管通用日志"); Context.Info("正在启动服务线程"); new Thread(() => _PerformAsPromoteProcess(args[1])) { Name = "Promote" }.Start(); Context.RequestStopLoading(); Context.DeclareStopped(); } else { Context.Info("当前进程为主进程"); IsCurrentProcessPromoted = false; // TODO 提权进程自动启动 } } [LifecycleStop] private static void _Stop() { if (_promotePipeServer is not null) { Context.Debug("正在结束提权管道服务"); _promotePipeServer.Dispose(); } if (_promoteProcess is not null && !_promoteProcess.WaitForExit(3000)) { Context.Debug("正在结束提权进程"); ProcessInterop.Kill(_promoteProcess, 0, true); } } }