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.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 */ }
}
}

View File

@@ -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,56 +83,61 @@ 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...");
using (tcp)
{
var stream = tcp.GetStream();
var lenBuf = new byte[4];
while (!ct.IsCancellationRequested && pipeServer.IsConnected && ws.State == WebSocketState.Open)
while (!ct.IsCancellationRequested && tcp.Connected && ws.State == WebSocketState.Open)
{
try
{
// 4-Byte Länge lesen
var read = 0;
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;
read += n;
}
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);
var n = await stream.ReadAsync(jpeg.AsMemory(read, jpegLen - read), ct);
if (n == 0) goto done;
read += n;
}
@@ -146,7 +152,8 @@ public class RtcService
await ws.SendAsync(new ArraySegment<byte>(payload), WebSocketMessageType.Text, true, CancellationToken.None);
}
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: