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,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<byte>(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<byte>(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: