From 0b76ef3bfd63f2f2a33bfbea3d957ef276220dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Gr=C3=BCssing?= Date: Thu, 11 Jun 2026 09:56:49 +0200 Subject: [PATCH] Fix pipe ACL: switch from named pipe to TCP loopback Named pipe created by SYSTEM service denied connection from user process. TCP loopback socket (127.0.0.1:random) has no cross-session ACL issues. Co-Authored-By: Claude Sonnet 4.6 --- agent-cs/CaptureModeRunner.cs | 25 ++++----- agent-cs/Services/RtcService.cs | 91 ++++++++++++++++++--------------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/agent-cs/CaptureModeRunner.cs b/agent-cs/CaptureModeRunner.cs index 4442a5e..dd3055f 100644 --- a/agent-cs/CaptureModeRunner.cs +++ b/agent-cs/CaptureModeRunner.cs @@ -1,24 +1,25 @@ using System.Drawing; using System.Drawing.Imaging; -using System.IO.Pipes; +using System.Net.Sockets; namespace ITNexusAgent; // Läuft als User-Prozess (via schtasks), nicht als SYSTEM-Service -// Captured den Desktop und schickt JPEG-Frames via Named Pipe an den Service +// Captured den Desktop und schickt JPEG-Frames via TCP Loopback an den Service public static class CaptureModeRunner { private static readonly ImageCodecInfo JpegCodec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid); - public static void Run(string pipeName) + public static void Run(string portStr) { - if (string.IsNullOrEmpty(pipeName)) return; + if (!int.TryParse(portStr, out var port) || port <= 0) return; try { - using var pipe = new NamedPipeClientStream(".", pipeName, PipeDirection.Out); - pipe.Connect(5000); // 5s timeout + using var tcp = new TcpClient(); + tcp.Connect("127.0.0.1", port); + var stream = tcp.GetStream(); var encParams = new EncoderParameters(1); encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 60L); @@ -32,7 +33,7 @@ public static class CaptureModeRunner int totalW = right - left; int totalH = bottom - top; - while (pipe.IsConnected) + while (tcp.Connected) { try { @@ -47,16 +48,16 @@ public static class CaptureModeRunner jpeg = ms.ToArray(); } - // Länge (4 Bytes LE) + JPEG-Daten - pipe.Write(BitConverter.GetBytes(jpeg.Length)); - pipe.Write(jpeg); - pipe.Flush(); + // 4-Byte Länge (LE) + JPEG-Daten + stream.Write(BitConverter.GetBytes(jpeg.Length)); + stream.Write(jpeg); + stream.Flush(); } catch { break; } Thread.Sleep(150); // ~6-7 fps } } - catch { /* Pipe nicht verfügbar oder Timeout → Exit */ } + catch { /* Verbindung fehlgeschlagen → Exit */ } } } diff --git a/agent-cs/Services/RtcService.cs b/agent-cs/Services/RtcService.cs index 790f5de..d024faf 100644 --- a/agent-cs/Services/RtcService.cs +++ b/agent-cs/Services/RtcService.cs @@ -1,5 +1,6 @@ using System.Diagnostics; -using System.IO.Pipes; +using System.Net; +using System.Net.Sockets; using System.Net.WebSockets; using System.Text; using Newtonsoft.Json; @@ -82,71 +83,77 @@ public class RtcService private async Task CapturePipeLoopAsync(ClientWebSocket ws, CancellationToken ct) { - var pipeName = $"it-nexus-rdp-{Guid.NewGuid():N}"; var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName; - using var pipeServer = new NamedPipeServerStream( - pipeName, PipeDirection.In, 1, - PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + // TCP Loopback: kein ACL-Problem zwischen SYSTEM-Service und User-Prozess + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; - // Helper via schtasks in User-Session starten - if (!SpawnCaptureHelper(exePath, pipeName)) + if (!SpawnCaptureHelper(exePath, port.ToString())) { + listener.Stop(); AgentWorker.Log("RDP: Helper-Start fehlgeschlagen"); return; } + TcpClient? tcp = null; try { - using var connectCts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + using var connectCts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, connectCts.Token); - await pipeServer.WaitForConnectionAsync(linked.Token); + tcp = await listener.AcceptTcpClientAsync(linked.Token); } catch { + listener.Stop(); AgentWorker.Log("RDP: Helper hat sich nicht verbunden"); return; } + finally { listener.Stop(); } - AgentWorker.Log("RDP: Helper verbunden, sende Frames..."); + AgentWorker.Log($"RDP: Helper verbunden (Port {port}), sende Frames..."); - var lenBuf = new byte[4]; - while (!ct.IsCancellationRequested && pipeServer.IsConnected && ws.State == WebSocketState.Open) + using (tcp) { - try + var stream = tcp.GetStream(); + var lenBuf = new byte[4]; + + while (!ct.IsCancellationRequested && tcp.Connected && ws.State == WebSocketState.Open) { - // 4-Byte Länge lesen - var read = 0; - while (read < 4) + try { - var n = await pipeServer.ReadAsync(lenBuf.AsMemory(read, 4 - read), ct); - if (n == 0) goto done; - read += n; + var read = 0; + while (read < 4) + { + var n = await stream.ReadAsync(lenBuf.AsMemory(read, 4 - read), ct); + if (n == 0) goto done; + read += n; + } + var jpegLen = BitConverter.ToInt32(lenBuf); + if (jpegLen <= 0 || jpegLen > 5_000_000) continue; + + var jpeg = new byte[jpegLen]; + read = 0; + while (read < jpegLen) + { + var n = await stream.ReadAsync(jpeg.AsMemory(read, jpegLen - read), ct); + if (n == 0) goto done; + read += n; + } + + var payload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new + { + type = "rdp_frame", + data = Convert.ToBase64String(jpeg) + })); + + if (ws.State == WebSocketState.Open) + await ws.SendAsync(new ArraySegment(payload), WebSocketMessageType.Text, true, CancellationToken.None); } - var jpegLen = BitConverter.ToInt32(lenBuf); - if (jpegLen <= 0 || jpegLen > 5_000_000) continue; - - // JPEG-Daten lesen - var jpeg = new byte[jpegLen]; - read = 0; - while (read < jpegLen) - { - var n = await pipeServer.ReadAsync(jpeg.AsMemory(read, jpegLen - read), ct); - if (n == 0) goto done; - read += n; - } - - var payload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new - { - type = "rdp_frame", - data = Convert.ToBase64String(jpeg) - })); - - if (ws.State == WebSocketState.Open) - await ws.SendAsync(new ArraySegment(payload), WebSocketMessageType.Text, true, CancellationToken.None); + catch (OperationCanceledException) { break; } + catch (Exception ex) { AgentWorker.Log($"RDP: TCP-Fehler: {ex.Message}"); break; } } - catch (OperationCanceledException) { break; } - catch (Exception ex) { AgentWorker.Log($"RDP: Pipe-Fehler: {ex.Message}"); break; } } done: