本機(jī)流量劫持
通過(guò)系統(tǒng)開(kāi)啟手動(dòng)代理
通過(guò)c#程序打開(kāi)Windows的手動(dòng)代理, 并且設(shè)置端口號(hào)和IP地址,這樣只要客戶端監(jiān)聽(tīng)該端口就可以獲取到本機(jī)的Http的流量數(shù)據(jù)。
通過(guò)對(duì)注冊(cè)表的修改,來(lái)開(kāi)啟本機(jī)的手動(dòng)代理,并且設(shè)置端口,Ip設(shè)置為本機(jī),因?yàn)榭蛻舳耸潜緳C(jī)啟動(dòng)的,端口設(shè)置不沖突的即可。
黑名單則是設(shè)置哪些域名或者IP段不走代理,我們這里先把局域網(wǎng)的排除掉。
[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>");
客戶端開(kāi)啟對(duì)應(yīng)TCP服務(wù)
_tcpListener = new TcpListener(IPAddress.Any, _httpProxyPort);
_tcpListener.Start();
主要就是開(kāi)啟一個(gè)監(jiān)聽(tīng)服務(wù),讓操作系統(tǒng)將對(duì)應(yīng)的流量轉(zhuǎn)發(fā)到我們的Socks5客戶端。
將http報(bào)文進(jìn)行解析,獲取到請(qǐng)求的targetHost和Port,這樣才能后面在和Socks5服務(wù)端握手的時(shí)候才能告知對(duì)方需要代理的遠(yuǎn)端信息。
解析系統(tǒng)的Http請(qǐng)求
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;
}
遠(yuǎn)程Socks5服務(wù)端握手
這里我們采用的是帶有認(rèn)證的握手,需要服務(wù)端也開(kāi)啟認(rèn)證配置,這里對(duì)于握手協(xié)議和認(rèn)證還不清楚的可以看我集合的上面一篇文章
將http的遠(yuǎn)程信息作為握手信息與服務(wù)端建立連接,讓服務(wù)端建立與目標(biāo)的連接代理。
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服務(wù)器不支持任何提供的認(rèn)證方法");
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用戶名/密碼認(rèn)證失敗");
}
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連接失敗 (狀態(tài)碼: {response[1]})");
}
交換流量
所謂的交換流量就是把遠(yuǎn)程代理的流量和本機(jī)的請(qǐng)求流量通過(guò)客戶端作為中間人來(lái)轉(zhuǎn)發(fā)
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)
代理流量顯示
客戶端也需要顯示上傳和下載流量的一些顯示,我們這里簡(jiǎn)單點(diǎn),因?yàn)槲覀冎伴_(kāi)發(fā)的服務(wù)端是有基于用戶的流量統(tǒng)計(jì)的,所以只需要把數(shù)據(jù)獲取到就行,一般情況下為了性能,也可以做雙端統(tǒng)計(jì)減少壓力。
我們這里通過(guò)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連接錯(cuò)誤: {ex.Message}");
}
}
驗(yàn)證
遠(yuǎn)端開(kāi)啟服務(wù)端

開(kāi)啟客戶端
添加一些配置
包括:服務(wù)端IP,服務(wù)端Port,本地代理Port,用戶名密碼

開(kāi)啟客戶端

可以看到代理成功,走的是本機(jī)的代理和服務(wù)端的代理請(qǐng)求

結(jié)尾
因?yàn)楸旧泶聿捎玫男薷南到y(tǒng)代理設(shè)置是一種基礎(chǔ)設(shè)置,所謂可能存在下面影響:
僅影響支持系統(tǒng)代理的應(yīng)用(部分UWP應(yīng)用、游戲等會(huì)繞過(guò)
無(wú)法代理非HTTP/HTTPS流量(如DNS、UDP
源碼地址
https://github.com/BruceQiu1996/Socks5Server
?轉(zhuǎn)自https://www.cnblogs.com/qwqwQAQ/p/18867762
該文章在 2025/5/12 9:12:31 編輯過(guò)