Files
PURErat-crack-scode/decompiled/PureCrack.Relay/TlsRelay.cs
T

290 lines
7.0 KiB
C#
Raw Normal View History

2026-08-27 10:56:38 -06:00
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using PureCrack.Crypto;
using PureCrack.Util;
namespace PureCrack.Relay;
public sealed class TlsRelay : IDisposable
{
private readonly X509Certificate2 _serverCert;
private readonly RouteHandlers _routes;
private readonly TcpListener _listener;
private readonly CancellationTokenSource _cts;
private Thread? _acceptThread;
private int _requestCount;
public const int DefaultPort = 443;
private const int MaxBodyBytes = 16777216;
public bool DynamicBuildEnabled { get; set; } = true;
public TlsRelay(X509Certificate2 serverCert, RouteHandlers routes, IPAddress? bindAddress = null, int port = 443)
{
_serverCert = serverCert ?? throw new ArgumentNullException("serverCert");
_routes = routes ?? throw new ArgumentNullException("routes");
_listener = new TcpListener(bindAddress ?? IPAddress.Any, port);
_cts = new CancellationTokenSource();
}
public void Start()
{
if (_acceptThread == null)
{
_listener.Start();
Log.Ok($"relay LISTEN on {_listener.LocalEndpoint}");
_acceptThread = new Thread(AcceptLoop)
{
IsBackground = true,
Name = "TlsRelay-accept"
};
_acceptThread.Start();
}
}
public void Stop()
{
if (!_cts.IsCancellationRequested)
{
_cts.Cancel();
try
{
_listener.Stop();
}
catch
{
}
_acceptThread?.Join(TimeSpan.FromSeconds(2.0));
Log.Info("relay stopped");
}
}
public void Dispose()
{
Stop();
_cts.Dispose();
_serverCert.Dispose();
}
private void AcceptLoop()
{
while (!_cts.IsCancellationRequested)
{
TcpClient state;
try
{
state = _listener.AcceptTcpClient();
}
catch (SocketException) when (_cts.IsCancellationRequested)
{
break;
}
catch (ObjectDisposedException)
{
break;
}
catch (Exception ex3)
{
Log.Err("accept: " + ex3.Message);
continue;
}
ThreadPool.QueueUserWorkItem(delegate(object obj)
{
HandleConnection((TcpClient)obj);
}, state);
}
}
private void HandleConnection(TcpClient client)
{
int num = Interlocked.Increment(ref _requestCount);
string arg = client.Client.RemoteEndPoint?.ToString() ?? "?";
Log.Section($"#{num} from {arg}");
try
{
client.ReceiveTimeout = 15000;
client.SendTimeout = 15000;
using SslStream sslStream = new SslStream(client.GetStream(), leaveInnerStreamOpen: false);
try
{
sslStream.AuthenticateAsServer(_serverCert, clientCertificateRequired: false, SslProtocols.Tls12, checkCertificateRevocation: false);
}
catch (Exception ex)
{
Log.Warn("TLS handshake failed: " + ex.Message);
return;
}
var (text, array) = ReadHttpRequest(sslStream);
Log.Bullet("path: " + text);
Log.Bullet($"body: {array.Length}b");
byte[] array2 = TryDecrypt(array);
if (array2 != null)
{
Log.Bullet($"decrypt OK: {array2.Length}b");
}
string path = CaptureWriter.Dump(text, array, array2);
Log.Bullet("dumped: " + Path.GetFileName(path));
var (array3, text2) = Route(text, array2);
Log.Bullet($"resp: {text2} ({array3.Length}b)");
SendResponse(sslStream, array3);
Log.Ok("sent " + text2);
}
catch (Exception ex2)
{
Log.Err("handler: " + ex2.Message);
}
finally
{
try
{
client.Close();
}
catch
{
}
}
}
private static (string path, byte[] body) ReadHttpRequest(Stream s)
{
using MemoryStream memoryStream = new MemoryStream();
byte[] array = new byte[4096];
int num;
for (num = -1; num < 0; num = FindHeaderEnd(memoryStream.GetBuffer(), (int)memoryStream.Length))
{
int num2 = s.Read(array, 0, array.Length);
if (num2 <= 0)
{
break;
}
memoryStream.Write(array, 0, num2);
if (memoryStream.Length > 65536)
{
throw new InvalidOperationException("HTTP headers exceed 64 KB");
}
}
if (num < 0)
{
throw new InvalidOperationException("HTTP request truncated before \\r\\n\\r\\n");
}
byte[] array2 = new byte[num];
Buffer.BlockCopy(memoryStream.GetBuffer(), 0, array2, 0, num);
string text = Encoding.UTF8.GetString(array2);
int result = 0;
string item = "/";
string[] array3 = text.Split(new string[1] { "\r\n" }, StringSplitOptions.None);
foreach (string text2 in array3)
{
if (text2.StartsWith("POST ", StringComparison.Ordinal) || text2.StartsWith("GET ", StringComparison.Ordinal))
{
string[] array4 = text2.Split(new char[1] { ' ' });
if (array4.Length >= 2)
{
item = array4[1];
}
}
else if (text2.StartsWith("Content-Length:", StringComparison.OrdinalIgnoreCase))
{
int.TryParse(text2.Substring("Content-Length:".Length).Trim(), out result);
}
}
if (result < 0 || result > 16777216)
{
throw new InvalidOperationException($"refusing body of size {result}");
}
int num3 = num + 4;
int num4 = (int)memoryStream.Length - num3;
byte[] array5 = new byte[result];
if (num4 > 0)
{
int num5 = Math.Min(num4, result);
Buffer.BlockCopy(memoryStream.GetBuffer(), num3, array5, 0, num5);
num4 = num5;
}
int j;
int num6;
for (j = Math.Max(0, num4); j < result; j += num6)
{
num6 = s.Read(array5, j, result - j);
if (num6 <= 0)
{
break;
}
}
if (j < result)
{
Log.Warn($"body truncated: got {j} of {result}");
}
return (path: item, body: array5);
}
private static int FindHeaderEnd(byte[] buf, int len)
{
for (int i = 0; i <= len - 4; i++)
{
if (buf[i] == 13 && buf[i + 1] == 10 && buf[i + 2] == 13 && buf[i + 3] == 10)
{
return i;
}
}
return -1;
}
private static byte[]? TryDecrypt(byte[] body)
{
if (body.Length < 32)
{
return null;
}
try
{
return Symmetric.AesDecryptFraming(body);
}
catch (Exception ex)
{
Log.Warn("decrypt err: " + ex.Message);
return null;
}
}
private (byte[] body, string label) Route(string path, byte[]? plaintext)
{
if (path.IndexOf("/validate", StringComparison.OrdinalIgnoreCase) >= 0)
{
return (body: _routes.ValidatePb, label: "validate");
}
if (path.IndexOf("/compile", StringComparison.OrdinalIgnoreCase) >= 0)
{
return (body: _routes.Compile(plaintext, DynamicBuildEnabled), label: (plaintext != null && DynamicBuildEnabled) ? "compile-dynamic" : "compile-canned");
}
if (path.IndexOf("/heartbeat", StringComparison.OrdinalIgnoreCase) >= 0 || path.IndexOf("/update-plugins", StringComparison.OrdinalIgnoreCase) >= 0)
{
return (body: RouteHandlers.AckResponse(), label: "ack");
}
return (body: _routes.ValidatePb, label: "fallback-validate");
}
private static void SendResponse(Stream s, byte[] responsePb)
{
byte[] array = Symmetric.AesEncryptFraming(responsePb);
string s2 = "HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\n" + $"Content-Length: {array.Length}\r\n" + "Connection: close\r\n\r\n";
byte[] bytes = Encoding.ASCII.GetBytes(s2);
s.Write(bytes, 0, bytes.Length);
s.Write(array, 0, array.Length);
s.Flush();
}
}