Files
anti-syktsu-proxy/Program.cs
2026-04-09 18:32:01 +02:00

336 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Runtime.InteropServices;
class Program
{
static string USER = "user";
static string PASS = "pass";
static async Task Main()
{
int port = 8888;
Console.WriteLine($"Local proxy:");
Console.WriteLine($" http://127.0.0.1:{port}");
Console.WriteLine($" login: {USER}");
Console.WriteLine($" pass : {PASS}");
var listener = new TcpListener(IPAddress.Any, port);
listener.Start();
while (true)
{
var client = await listener.AcceptTcpClientAsync();
_ = Task.Run(() => HandleClient(client));
}
}
static async Task HandleClient(TcpClient client)
{
using (client)
{
var stream = client.GetStream();
var request = await ReadHeaders(stream);
if (request == null) return;
if (!CheckAuth(request.HeadersRaw))
{
await Write407(stream);
return;
}
if (request.Method == "CONNECT")
{
await HandleConnect(stream, request.Target);
}
else
{
await HandleHttp(stream, request);
}
}
}
// ================= HTTP =================
static async Task HandleHttp(NetworkStream clientStream, HttpRequestData req)
{
DefaultProxyCredentials = CredentialCache.DefaultCredentials
var handler = new SocketsHttpHandler
{
UseProxy = true,
Proxy = new WinHttpProxy(),
DefaultProxyCredentials = CredentialCache.DefaultCredentials,
AllowAutoRedirect = false
};
using var http = new HttpClient(handler);
var message = new HttpRequestMessage(new HttpMethod(req.Method), req.Url);
foreach (var h in req.Headers)
{
if (!h.Key.Equals("Proxy-Authorization", StringComparison.OrdinalIgnoreCase))
message.Headers.TryAddWithoutValidation(h.Key, h.Value);
}
if (req.Body != null)
message.Content = new StreamContent(req.Body);
var resp = await http.SendAsync(message, HttpCompletionOption.ResponseHeadersRead);
// статус
var statusLine = $"HTTP/1.1 {(int)resp.StatusCode} {resp.ReasonPhrase}\r\n";
await clientStream.WriteAsync(Encoding.ASCII.GetBytes(statusLine));
// заголовки
foreach (var h in resp.Headers)
{
foreach (var v in h.Value)
await WriteHeader(clientStream, h.Key, v);
}
foreach (var h in resp.Content.Headers)
{
foreach (var v in h.Value)
await WriteHeader(clientStream, h.Key, v);
}
await clientStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n"));
// тело (стриминг)
await resp.Content.CopyToAsync(clientStream);
}
// ================= CONNECT =================
static async Task HandleConnect(NetworkStream clientStream, string target)
{
var parts = target.Split(':');
string host = parts[0];
int port = int.Parse(parts[1]);
var proxy = new WinHttpProxy();
var proxyUri = proxy.GetProxy(new Uri($"https://{host}:{port}"));
using var upstream = new TcpClient();
await upstream.ConnectAsync(proxyUri.Host, proxyUri.Port);
var upstreamStream = upstream.GetStream();
// CONNECT к upstream proxy
string connectReq =
$"CONNECT {host}:{port} HTTP/1.1\r\n" +
$"Host: {host}:{port}\r\n" +
$"Proxy-Connection: Keep-Alive\r\n\r\n";
await upstreamStream.WriteAsync(Encoding.ASCII.GetBytes(connectReq));
// читаем ответ upstream полностью до \r\n\r\n
var resp = await ReadHeaders(upstreamStream);
if (!resp.HeadersRaw.Contains("200"))
{
await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 502 Bad Gateway\r\n\r\n"));
return;
}
// говорим клиенту ОК
await clientStream.WriteAsync(Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n"));
// туннель
var t1 = Pump(upstreamStream, clientStream);
var t2 = Pump(clientStream, upstreamStream);
await Task.WhenAny(t1, t2);
}
// ================= AUTH =================
static bool CheckAuth(string headers)
{
string expected = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{USER}:{PASS}"));
return headers.Contains($"Proxy-Authorization: Basic {expected}");
}
static async Task Write407(NetworkStream stream)
{
string resp =
"HTTP/1.1 407 Proxy Authentication Required\r\n" +
"Proxy-Authenticate: Basic realm=\"proxy\"\r\n\r\n";
await stream.WriteAsync(Encoding.ASCII.GetBytes(resp));
}
// ================= REQUEST PARSER =================
class HttpRequestData
{
public string Method;
public string Url;
public string Target;
public string HeadersRaw;
public Dictionary<string, string> Headers = new();
public Stream Body;
}
static async Task<HttpRequestData> ReadHeaders(NetworkStream stream)
{
var buffer = new byte[8192];
int read = await stream.ReadAsync(buffer);
if (read <= 0) return null;
string text = Encoding.ASCII.GetString(buffer, 0, read);
var lines = text.Split("\r\n");
var first = lines[0].Split(' ');
var data = new HttpRequestData
{
Method = first[0],
Target = first[1],
HeadersRaw = text
};
foreach (var line in lines.Skip(1))
{
if (string.IsNullOrWhiteSpace(line)) break;
int idx = line.IndexOf(':');
if (idx > 0)
data.Headers[line[..idx]] = line[(idx + 1)..].Trim();
}
if (data.Method != "CONNECT")
{
data.Url = data.Target.StartsWith("http")
? data.Target
: $"http://{data.Headers["Host"]}{data.Target}";
}
return data;
}
static async Task WriteHeader(NetworkStream stream, string key, string value)
{
string line = $"{key}: {value}\r\n";
await stream.WriteAsync(Encoding.ASCII.GetBytes(line));
}
}
// ================= WINHTTP PROXY =================
class WinHttpProxy : IWebProxy
{
public ICredentials Credentials { get; set; }
public Uri GetProxy(Uri destination)
{
return WinHttpHelper.GetProxyForUrl(destination);
}
public bool IsBypassed(Uri host) => false;
}
using System.Runtime.InteropServices;
static class WinHttpHelper
{
[DllImport("winhttp.dll", SetLastError = true)]
static extern IntPtr WinHttpOpen(string agent, int accessType, string proxy, string bypass, int flags);
[DllImport("winhttp.dll", SetLastError = true)]
static extern bool WinHttpGetProxyForUrl(
IntPtr hSession,
string url,
ref WINHTTP_AUTOPROXY_OPTIONS options,
out WINHTTP_PROXY_INFO proxyInfo);
[DllImport("winhttp.dll")]
static extern bool WinHttpCloseHandle(IntPtr handle);
public static Uri GetProxyForUrl(Uri url)
{
IntPtr session = WinHttpOpen("proxy", 0, null, null, 0);
var options = new WINHTTP_AUTOPROXY_OPTIONS
{
dwFlags = 0x00000001 | 0x00000002, // AUTO_DETECT + CONFIG_URL
dwAutoDetectFlags = 0x00000001 | 0x00000002 // DHCP + DNS_A
};
if (WinHttpGetProxyForUrl(session, url.ToString(), ref options, out var info))
{
string proxy = PtrToString(info.lpszProxy);
if (!string.IsNullOrEmpty(proxy))
{
var first = proxy.Split(';')[0];
return new Uri(Normalize(first));
}
}
WinHttpCloseHandle(session);
return url; // direct fallback
}
static string PtrToString(IntPtr ptr)
{
if (ptr == IntPtr.Zero) return null;
return Marshal.PtrToStringUni(ptr);
}
static string Normalize(string proxy)
{
if (!proxy.StartsWith("http"))
return "http://" + proxy;
return proxy;
}
struct WINHTTP_AUTOPROXY_OPTIONS
{
public int dwFlags;
public int dwAutoDetectFlags;
public IntPtr lpszAutoConfigUrl;
public IntPtr lpvReserved;
public int dwReserved;
public bool fAutoLogonIfChallenged;
}
struct WINHTTP_PROXY_INFO
{
public int dwAccessType;
public IntPtr lpszProxy;
public IntPtr lpszProxyBypass;
}
}
static async Task Pump(Stream from, Stream to)
{
var buffer = ArrayPool<byte>.Shared.Rent(8192);
try
{
while (true)
{
int read = await from.ReadAsync(buffer);
if (read <= 0) break;
await to.WriteAsync(buffer.AsMemory(0, read));
}
}
catch { }
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}