mirror of
https://github.com/Harrison-Wells-Cyber/PS-Proxy
synced 2026-07-26 08:06:34 +00:00
Make it actually work
This commit is contained in:
committed by
GitHub
parent
6b6247d797
commit
ac6a1914a8
@@ -1,41 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Security;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace PSProxy.Agent
|
||||
{
|
||||
public sealed class Tunnel
|
||||
{
|
||||
private const byte FrameOpen = 1, FrameOpenOK = 2, FrameOpenFail = 3, FrameData = 4, FrameClose = 5, FramePing = 6, FramePong = 7;
|
||||
private const int MaxPayload = 1 << 20;
|
||||
private readonly string server;
|
||||
private readonly int port;
|
||||
private readonly string certPin;
|
||||
private readonly string enrollToken;
|
||||
private readonly ConcurrentDictionary<ulong, StreamCtx> streams = new ConcurrentDictionary<ulong, StreamCtx>();
|
||||
private readonly object sendLock = new object();
|
||||
private SslStream tls;
|
||||
private volatile bool stopping;
|
||||
|
||||
public Tunnel(string server, int port, string certPin, string enrollToken)
|
||||
{
|
||||
this.server = server; this.port = port; this.certPin = NormalizeHex(certPin); this.enrollToken = enrollToken;
|
||||
this.server = server;
|
||||
this.port = port;
|
||||
this.certPin = NormalizeHex(certPin);
|
||||
this.enrollToken = enrollToken;
|
||||
if (this.certPin.Length != 64) throw new ArgumentException("CertPin must be a SHA-256 hex string");
|
||||
if (String.IsNullOrWhiteSpace(enrollToken)) throw new ArgumentException("EnrollToken is required");
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
Console.Error.WriteLine("[ps-proxy] connecting to {0}:{1}", server, port);
|
||||
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
|
||||
var req = (HttpWebRequest)WebRequest.Create("https://" + server + ":" + port + "/enroll");
|
||||
req.Method = "POST";
|
||||
req.Headers["X-PSProxy-Enrollment"] = enrollToken;
|
||||
req.ContentLength = 0;
|
||||
using (var resp = (HttpWebResponse)req.GetResponse())
|
||||
using (var tcp = new TcpClient())
|
||||
{
|
||||
if (resp.StatusCode != HttpStatusCode.OK) throw new Exception("server enrollment failed: " + resp.StatusCode);
|
||||
tcp.NoDelay = true;
|
||||
tcp.Connect(server, port);
|
||||
using (tls = new SslStream(tcp.GetStream(), false, ValidateServerCertificate))
|
||||
{
|
||||
tls.AuthenticateAsClient(server, null, SslProtocols.Tls12, false);
|
||||
WriteAscii("PSP1\nENROLL " + enrollToken + "\n");
|
||||
Frame hello = ReadFrame();
|
||||
if (hello.Type != FramePong || Encoding.ASCII.GetString(hello.Payload) != "OK") throw new Exception("server did not accept enrollment");
|
||||
Console.Error.WriteLine("[ps-proxy] enrolled and ready");
|
||||
ReadLoop();
|
||||
}
|
||||
}
|
||||
Console.Error.WriteLine("[ps-proxy] enrolled; tunnel protocol not implemented in this release scaffold");
|
||||
while (true) System.Threading.Thread.Sleep(60000);
|
||||
}
|
||||
|
||||
private void ReadLoop()
|
||||
{
|
||||
while (!stopping)
|
||||
{
|
||||
Frame f = ReadFrame();
|
||||
switch (f.Type)
|
||||
{
|
||||
case FrameOpen: HandleOpen(f.StreamID, Encoding.UTF8.GetString(f.Payload)); break;
|
||||
case FrameData: HandleData(f.StreamID, f.Payload); break;
|
||||
case FrameClose: CloseStream(f.StreamID, false); break;
|
||||
case FramePing: SendFrame(new Frame(f.StreamID, FramePong, new byte[0])); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOpen(ulong sid, string target)
|
||||
{
|
||||
try
|
||||
{
|
||||
string host; int dstPort; SplitTarget(target, out host, out dstPort);
|
||||
var client = new TcpClient();
|
||||
client.NoDelay = true;
|
||||
client.Connect(host, dstPort);
|
||||
var ctx = new StreamCtx(sid, client);
|
||||
if (!streams.TryAdd(sid, ctx)) { client.Close(); throw new Exception("duplicate stream"); }
|
||||
SendFrame(new Frame(sid, FrameOpenOK, new byte[0]));
|
||||
var t = new Thread(delegate() { PumpTargetToServer(ctx); });
|
||||
t.IsBackground = true;
|
||||
t.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SendFrame(new Frame(sid, FrameOpenFail, Encoding.UTF8.GetBytes(ex.Message)));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleData(ulong sid, byte[] payload)
|
||||
{
|
||||
StreamCtx ctx;
|
||||
if (!streams.TryGetValue(sid, out ctx)) { SendFrame(new Frame(sid, FrameClose, new byte[0])); return; }
|
||||
try { ctx.Stream.Write(payload, 0, payload.Length); }
|
||||
catch { CloseStream(sid, true); }
|
||||
}
|
||||
|
||||
private void PumpTargetToServer(StreamCtx ctx)
|
||||
{
|
||||
byte[] buf = new byte[32768];
|
||||
try
|
||||
{
|
||||
while (!stopping)
|
||||
{
|
||||
int n = ctx.Stream.Read(buf, 0, buf.Length);
|
||||
if (n <= 0) break;
|
||||
byte[] payload = new byte[n];
|
||||
Buffer.BlockCopy(buf, 0, payload, 0, n);
|
||||
SendFrame(new Frame(ctx.StreamID, FrameData, payload));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { CloseStream(ctx.StreamID, true); }
|
||||
}
|
||||
|
||||
private void CloseStream(ulong sid, bool notify)
|
||||
{
|
||||
StreamCtx ctx;
|
||||
if (streams.TryRemove(sid, out ctx))
|
||||
{
|
||||
try { ctx.Client.Close(); } catch { }
|
||||
if (notify) { try { SendFrame(new Frame(sid, FrameClose, new byte[0])); } catch { } }
|
||||
}
|
||||
}
|
||||
|
||||
private void SendFrame(Frame f)
|
||||
{
|
||||
if (f.Payload == null) f.Payload = new byte[0];
|
||||
if (f.Payload.Length > MaxPayload) throw new InvalidOperationException("frame payload too large");
|
||||
byte[] hdr = new byte[13];
|
||||
WriteU64BE(hdr, 0, f.StreamID);
|
||||
hdr[8] = f.Type;
|
||||
WriteI32BE(hdr, 9, f.Payload.Length);
|
||||
lock (sendLock)
|
||||
{
|
||||
tls.Write(hdr, 0, hdr.Length);
|
||||
if (f.Payload.Length > 0) tls.Write(f.Payload, 0, f.Payload.Length);
|
||||
tls.Flush();
|
||||
}
|
||||
}
|
||||
|
||||
private Frame ReadFrame()
|
||||
{
|
||||
byte[] hdr = ReadExact(13);
|
||||
ulong sid = ReadU64BE(hdr, 0);
|
||||
byte typ = hdr[8];
|
||||
int len = ReadI32BE(hdr, 9);
|
||||
if (len < 0 || len > MaxPayload) throw new IOException("frame payload too large: " + len);
|
||||
return new Frame(sid, typ, len == 0 ? new byte[0] : ReadExact(len));
|
||||
}
|
||||
|
||||
private byte[] ReadExact(int n)
|
||||
{
|
||||
byte[] b = new byte[n];
|
||||
int off = 0;
|
||||
while (off < n)
|
||||
{
|
||||
int got = tls.Read(b, off, n - off);
|
||||
if (got <= 0) throw new EndOfStreamException();
|
||||
off += got;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private void WriteAscii(string s)
|
||||
{
|
||||
byte[] b = Encoding.ASCII.GetBytes(s);
|
||||
tls.Write(b, 0, b.Length);
|
||||
tls.Flush();
|
||||
}
|
||||
|
||||
private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
var cert2 = new X509Certificate2(certificate);
|
||||
@@ -46,8 +182,36 @@ namespace PSProxy.Agent
|
||||
}
|
||||
return sslPolicyErrors == SslPolicyErrors.None;
|
||||
}
|
||||
private static void SendLine(Stream s, string line) { var b = Encoding.ASCII.GetBytes(line + "\n"); s.Write(b,0,b.Length); s.Flush(); }
|
||||
private static string ReadLine(Stream s) { using (var ms = new MemoryStream()) { int c; while ((c=s.ReadByte())>=0) { if (c=='\n') break; if (c!='\r') ms.WriteByte((byte)c); } return Encoding.ASCII.GetString(ms.ToArray()); } }
|
||||
|
||||
private static void SplitTarget(string target, out string host, out int dstPort)
|
||||
{
|
||||
int idx = target.LastIndexOf(':');
|
||||
if (idx <= 0 || idx == target.Length - 1) throw new ArgumentException("target must be host:port");
|
||||
host = target.Substring(0, idx);
|
||||
dstPort = Int32.Parse(target.Substring(idx + 1));
|
||||
if (dstPort < 1 || dstPort > 65535) throw new ArgumentException("invalid port");
|
||||
}
|
||||
|
||||
private static string NormalizeHex(string s) { return (s ?? "").Trim().ToLowerInvariant().Replace(":", "").Replace(" ", ""); }
|
||||
private static void WriteI32BE(byte[] b, int o, int v) { b[o] = (byte)(v >> 24); b[o + 1] = (byte)(v >> 16); b[o + 2] = (byte)(v >> 8); b[o + 3] = (byte)v; }
|
||||
private static int ReadI32BE(byte[] b, int o) { return ((int)b[o] << 24) | ((int)b[o + 1] << 16) | ((int)b[o + 2] << 8) | (int)b[o + 3]; }
|
||||
private static void WriteU64BE(byte[] b, int o, ulong v) { for (int i = 7; i >= 0; i--) { b[o + i] = (byte)v; v >>= 8; } }
|
||||
private static ulong ReadU64BE(byte[] b, int o) { ulong v = 0; for (int i = 0; i < 8; i++) v = (v << 8) | b[o + i]; return v; }
|
||||
}
|
||||
|
||||
public sealed class StreamCtx
|
||||
{
|
||||
public readonly ulong StreamID;
|
||||
public readonly TcpClient Client;
|
||||
public readonly NetworkStream Stream;
|
||||
public StreamCtx(ulong streamID, TcpClient client) { StreamID = streamID; Client = client; Stream = client.GetStream(); }
|
||||
}
|
||||
|
||||
public struct Frame
|
||||
{
|
||||
public ulong StreamID;
|
||||
public byte Type;
|
||||
public byte[] Payload;
|
||||
public Frame(ulong streamID, byte type, byte[] payload) { StreamID = streamID; Type = type; Payload = payload; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user