本機流量劫持
通過系統開啟手動代理
通過c#程序打開Windows的手動代理, 并且設置端口號和IP地址,這樣只要客戶端監聽該端口就可以獲取到本機的Http的流量數據。
通過對注冊表的修改,來開啟本機的手動代理,并且設置端口,Ip設置為本機,因為客戶端是本機啟動的,端口設置不沖突的即可。
黑名單則是設置哪些域名或者IP段不走代理,我們這里先把局域網的排除掉。
[DllImport("wininet.dll")]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
public const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
public const int INTERNET_OPTION_REFRESH = 37;
public static void SetProxy(string proxyServer, bool enable)
{
const string userRoot = "HKEY_CURRENT_USER";
const string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const string keyName = userRoot + "\\" + subkey;
Microsoft.Win32.Registry.SetValue(keyName, "ProxyServer", proxyServer);
Microsoft.Win32.Registry.SetValue(keyName, "ProxyEnable", enable ? 1 : 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
}
public static void SetProxyExceptions(string exceptions)
{
const string userRoot = "HKEY_CURRENT_USER";
const string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const string keyName = userRoot + "\\" + subkey;
Microsoft.Win32.Registry.SetValue(keyName, "ProxyOverride", exceptions);
}
SystemProxy.SetProxy($"127.0.0.1:{App.SettingsModel.LocalPort}", true);
SystemProxy.SetProxyExceptions("localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*;<local>");
客戶端開啟對應TCP服務
_tcpListener = new TcpListener(IPAddress.Any, _httpProxyPort);
_tcpListener.Start();
主要就是開啟一個監聽服務,讓操作系統將對應的流量轉發到我們的Socks5客戶端。
將http報文進行解析,獲取到請求的targetHost和Port,這樣才能后面在和Socks5服務端握手的時候才能告知對方需要代理的遠端信息。
解析系統的Http請求
private bool TryParseHttpRequest(string request, out string host, out int port)
{
host = null;
port = 0;
if (request.StartsWith("CONNECT"))
{
var parts = request.Split(' ')[1].Split(':');
host = parts[0];
port = int.Parse(parts[1]);
return true;
}
using (var reader = new StringReader(request))
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line.StartsWith("Host:", StringComparison.OrdinalIgnoreCase))
{
var hostParts = line.Substring(5).Trim().Split(':');
host = hostParts[0];
if (hostParts.Length > 1)
port = int.Parse(hostParts[1]);
return true;
}
if (string.IsNullOrWhiteSpace(line))
break;
}
}
if (request.StartsWith("GET ") || request.StartsWith("POST "))
{
var url = request.Split(' ')[1];
if (Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
host = uri.Host;
port = uri.Port;
return true;
}
}
return false;
}
遠程Socks5服務端握手
這里我們采用的是帶有認證的握手,需要服務端也開啟認證配置,這里對于握手協議和認證還不清楚的可以看我集合的上面一篇文章
將http的遠程信息作為握手信息與服務端建立連接,讓服務端建立與目標的連接代理。
private async Task PerformSocks5Handshake(NetworkStream socks5Stream,
string targetHost,
int targetPort,
string username,
string password)
{
var authMethods = new byte[] { 0x05, 0x02, 0x00, 0x02 };
await socks5Stream.WriteAsync(authMethods, 0, authMethods.Length);
var authResponse = new byte[2];
await socks5Stream.ReadAsync(authResponse, 0, 2);
if (authResponse[1] == 0xFF)
throw new Exception("SOCKS5服務器不支持任何提供的認證方法");
if (authResponse[1] == 0x02)
{
var authRequest = new byte[3 + username.Length + password.Length];
authRequest[0] = 0x01;
authRequest[1] = (byte)username.Length;
Encoding.ASCII.GetBytes(username).CopyTo(authRequest, 2);
authRequest[2 + username.Length] = (byte)password.Length;
Encoding.ASCII.GetBytes(password).CopyTo(authRequest, 3 + username.Length);
await socks5Stream.WriteAsync(authRequest, 0, authRequest.Length);
var authResult = new byte[2];
await socks5Stream.ReadAsync(authResult, 0, 2);
if (authResult[1] != 0x00)
throw new Exception("SOCKS5用戶名/密碼認證失敗");
}
var request = new byte[7 + targetHost.Length];
request[0] = 0x05;
request[1] = 0x01;
request[2] = 0x00;
request[3] = 0x03;
request[4] = (byte)targetHost.Length;
Encoding.ASCII.GetBytes(targetHost).CopyTo(request, 5);
BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)targetPort)).CopyTo(request, 5 + targetHost.Length);
await socks5Stream.WriteAsync(request, 0, request.Length);
var response = new byte[10];
await socks5Stream.ReadAsync(response, 0, 10);
if (response[1] != 0x00)
throw new Exception($"SOCKS5連接失敗 (狀態碼: {response[1]})");
}
交換流量
所謂的交換流量就是把遠程代理的流量和本機的請求流量通過客戶端作為中間人來轉發
private async Task ForwardDataAsync(NetworkStream src, NetworkStream dest)
{
var buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await src.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await dest.WriteAsync(buffer, 0, bytesRead);
}
}
await Task.WhenAny(ForwardDataAsync(httpStream, socks5Stream),ForwardDataAsync(socks5Stream, httpStream)
代理流量顯示
客戶端也需要顯示上傳和下載流量的一些顯示,我們這里簡單點,因為我們之前開發的服務端是有基于用戶的流量統計的,所以只需要把數據獲取到就行,一般情況下為了性能,也可以做雙端統計減少壓力。
我們這里通過SSE將用戶的流量信息基于用戶名推送到客戶端。
public async Task ConnectAsync(string remoteAddress, string userName)
{
_httpClient = new HttpClient(new HttpClientHandler
{
Proxy = new WebProxy($"http://{remoteAddress}:5000"),
});
_cts = new CancellationTokenSource();
try
{
using var response = await _httpClient
.GetAsync($"http://{remoteAddress}:5000/account/flow/{userName}", HttpCompletionOption.ResponseHeadersRead,_cts.Token);
if (response.IsSuccessStatusCode)
{
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
while (!_cts.Token.IsCancellationRequested)
{
var line = await reader.ReadLineAsync();
if (!string.IsNullOrEmpty(line))
{
MessageReceived?.Invoke(line);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"SSE連接錯誤: {ex.Message}");
}
}
驗證
遠端開啟服務端

開啟客戶端
添加一些配置
包括:服務端IP,服務端Port,本地代理Port,用戶名密碼

開啟客戶端

可以看到代理成功,走的是本機的代理和服務端的代理請求

結尾
因為本身代理采用的修改系統代理設置是一種基礎設置,所謂可能存在下面影響:
僅影響支持系統代理的應用(部分UWP應用、游戲等會繞過
無法代理非HTTP/HTTPS流量(如DNS、UDP
源碼地址
https://github.com/BruceQiu1996/Socks5Server
?轉自https://www.cnblogs.com/qwqwQAQ/p/18867762
該文章在 2025/5/12 9:12:31 編輯過