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 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 09:56:49 +02:00
parent 45f4c7846e
commit 0b76ef3bfd
2 changed files with 62 additions and 54 deletions

View File

@@ -1,24 +1,25 @@
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO.Pipes; using System.Net.Sockets;
namespace ITNexusAgent; namespace ITNexusAgent;
// Läuft als User-Prozess (via schtasks), nicht als SYSTEM-Service // 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 public static class CaptureModeRunner
{ {
private static readonly ImageCodecInfo JpegCodec = private static readonly ImageCodecInfo JpegCodec =
ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid); 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 try
{ {
using var pipe = new NamedPipeClientStream(".", pipeName, PipeDirection.Out); using var tcp = new TcpClient();
pipe.Connect(5000); // 5s timeout tcp.Connect("127.0.0.1", port);
var stream = tcp.GetStream();
var encParams = new EncoderParameters(1); var encParams = new EncoderParameters(1);
encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 60L); encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 60L);
@@ -32,7 +33,7 @@ public static class CaptureModeRunner
int totalW = right - left; int totalW = right - left;
int totalH = bottom - top; int totalH = bottom - top;
while (pipe.IsConnected) while (tcp.Connected)
{ {
try try
{ {
@@ -47,16 +48,16 @@ public static class CaptureModeRunner
jpeg = ms.ToArray(); jpeg = ms.ToArray();
} }
// Länge (4 Bytes LE) + JPEG-Daten // 4-Byte Länge (LE) + JPEG-Daten
pipe.Write(BitConverter.GetBytes(jpeg.Length)); stream.Write(BitConverter.GetBytes(jpeg.Length));
pipe.Write(jpeg); stream.Write(jpeg);
pipe.Flush(); stream.Flush();
} }
catch { break; } catch { break; }
Thread.Sleep(150); // ~6-7 fps Thread.Sleep(150); // ~6-7 fps
} }
} }
catch { /* Pipe nicht verfügbar oder Timeout → Exit */ } catch { /* Verbindung fehlgeschlagen → Exit */ }
} }
} }

View File

@@ -1,5 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.IO.Pipes; using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text; using System.Text;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -82,56 +83,61 @@ public class RtcService
private async Task CapturePipeLoopAsync(ClientWebSocket ws, CancellationToken ct) private async Task CapturePipeLoopAsync(ClientWebSocket ws, CancellationToken ct)
{ {
var pipeName = $"it-nexus-rdp-{Guid.NewGuid():N}";
var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName; var exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule!.FileName;
using var pipeServer = new NamedPipeServerStream( // TCP Loopback: kein ACL-Problem zwischen SYSTEM-Service und User-Prozess
pipeName, PipeDirection.In, 1, var listener = new TcpListener(IPAddress.Loopback, 0);
PipeTransmissionMode.Byte, PipeOptions.Asynchronous); listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
// Helper via schtasks in User-Session starten if (!SpawnCaptureHelper(exePath, port.ToString()))
if (!SpawnCaptureHelper(exePath, pipeName))
{ {
listener.Stop();
AgentWorker.Log("RDP: Helper-Start fehlgeschlagen"); AgentWorker.Log("RDP: Helper-Start fehlgeschlagen");
return; return;
} }
TcpClient? tcp = null;
try 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); using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, connectCts.Token);
await pipeServer.WaitForConnectionAsync(linked.Token); tcp = await listener.AcceptTcpClientAsync(linked.Token);
} }
catch catch
{ {
listener.Stop();
AgentWorker.Log("RDP: Helper hat sich nicht verbunden"); AgentWorker.Log("RDP: Helper hat sich nicht verbunden");
return; return;
} }
finally { listener.Stop(); }
AgentWorker.Log("RDP: Helper verbunden, sende Frames..."); AgentWorker.Log($"RDP: Helper verbunden (Port {port}), sende Frames...");
using (tcp)
{
var stream = tcp.GetStream();
var lenBuf = new byte[4]; var lenBuf = new byte[4];
while (!ct.IsCancellationRequested && pipeServer.IsConnected && ws.State == WebSocketState.Open)
while (!ct.IsCancellationRequested && tcp.Connected && ws.State == WebSocketState.Open)
{ {
try try
{ {
// 4-Byte Länge lesen
var read = 0; var read = 0;
while (read < 4) while (read < 4)
{ {
var n = await pipeServer.ReadAsync(lenBuf.AsMemory(read, 4 - read), ct); var n = await stream.ReadAsync(lenBuf.AsMemory(read, 4 - read), ct);
if (n == 0) goto done; if (n == 0) goto done;
read += n; read += n;
} }
var jpegLen = BitConverter.ToInt32(lenBuf); var jpegLen = BitConverter.ToInt32(lenBuf);
if (jpegLen <= 0 || jpegLen > 5_000_000) continue; if (jpegLen <= 0 || jpegLen > 5_000_000) continue;
// JPEG-Daten lesen
var jpeg = new byte[jpegLen]; var jpeg = new byte[jpegLen];
read = 0; read = 0;
while (read < jpegLen) while (read < jpegLen)
{ {
var n = await pipeServer.ReadAsync(jpeg.AsMemory(read, jpegLen - read), ct); var n = await stream.ReadAsync(jpeg.AsMemory(read, jpegLen - read), ct);
if (n == 0) goto done; if (n == 0) goto done;
read += n; read += n;
} }
@@ -146,7 +152,8 @@ public class RtcService
await ws.SendAsync(new ArraySegment<byte>(payload), WebSocketMessageType.Text, true, CancellationToken.None); await ws.SendAsync(new ArraySegment<byte>(payload), WebSocketMessageType.Text, true, CancellationToken.None);
} }
catch (OperationCanceledException) { break; } catch (OperationCanceledException) { break; }
catch (Exception ex) { AgentWorker.Log($"RDP: Pipe-Fehler: {ex.Message}"); break; } catch (Exception ex) { AgentWorker.Log($"RDP: TCP-Fehler: {ex.Message}"); break; }
}
} }
done: done: