init: socks5 proxy with tls clienthello fragmentation
This commit is contained in:
commit
a1200d20d1
7 changed files with 349 additions and 0 deletions
219
src/main.cpp
Normal file
219
src/main.cpp
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "sni_split.h"
|
||||
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
|
||||
struct Socks5Target {
|
||||
std::string host;
|
||||
uint16_t port = 0;
|
||||
};
|
||||
|
||||
static void close_sock(SOCKET s) {
|
||||
if (s != INVALID_SOCKET) closesocket(s);
|
||||
}
|
||||
|
||||
static bool read_exact(SOCKET s, uint8_t* buf, int len) {
|
||||
int got = 0;
|
||||
while (got < len) {
|
||||
int n = recv(s, reinterpret_cast<char*>(buf + got), len - got, 0);
|
||||
if (n <= 0) return false;
|
||||
got += n;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool send_all(SOCKET s, const uint8_t* data, size_t len) {
|
||||
size_t sent = 0;
|
||||
while (sent < len) {
|
||||
int n = send(s, reinterpret_cast<const char*>(data + sent), static_cast<int>(len - sent), 0);
|
||||
if (n <= 0) return false;
|
||||
sent += static_cast<size_t>(n);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void relay(SOCKET from, SOCKET to) {
|
||||
std::vector<char> buf(16384);
|
||||
while (true) {
|
||||
int n = recv(from, buf.data(), static_cast<int>(buf.size()), 0);
|
||||
if (n <= 0) break;
|
||||
if (!send_all(to, reinterpret_cast<uint8_t*>(buf.data()), static_cast<size_t>(n))) break;
|
||||
}
|
||||
shutdown(to, SD_SEND);
|
||||
}
|
||||
|
||||
static bool socks5_handshake(SOCKET client, Socks5Target& target) {
|
||||
uint8_t hdr[2];
|
||||
if (!read_exact(client, hdr, 2)) return false;
|
||||
if (hdr[0] != 0x05) return false;
|
||||
|
||||
std::vector<uint8_t> methods(hdr[1]);
|
||||
if (hdr[1] > 0 && !read_exact(client, methods.data(), hdr[1])) return false;
|
||||
|
||||
uint8_t reply[2] = { 0x05, 0x00 };
|
||||
if (send(client, reinterpret_cast<char*>(reply), 2, 0) != 2) return false;
|
||||
|
||||
uint8_t req[4];
|
||||
if (!read_exact(client, req, 4)) return false;
|
||||
if (req[0] != 0x05 || req[1] != 0x01) return false;
|
||||
|
||||
uint8_t atyp = req[3];
|
||||
if (atyp == 0x01) {
|
||||
uint8_t addr[4];
|
||||
if (!read_exact(client, addr, 4)) return false;
|
||||
char ipstr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, addr, ipstr, sizeof(ipstr));
|
||||
target.host = ipstr;
|
||||
} else if (atyp == 0x03) {
|
||||
uint8_t len;
|
||||
if (!read_exact(client, &len, 1)) return false;
|
||||
std::vector<uint8_t> domain(len);
|
||||
if (len > 0 && !read_exact(client, domain.data(), len)) return false;
|
||||
target.host.assign(domain.begin(), domain.end());
|
||||
} else if (atyp == 0x04) {
|
||||
uint8_t addr[16];
|
||||
if (!read_exact(client, addr, 16)) return false;
|
||||
char ipstr[INET6_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET6, addr, ipstr, sizeof(ipstr));
|
||||
target.host = ipstr;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t portb[2];
|
||||
if (!read_exact(client, portb, 2)) return false;
|
||||
target.port = (static_cast<uint16_t>(portb[0]) << 8) | portb[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static SOCKET connect_target(const Socks5Target& target) {
|
||||
addrinfo hints{};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
addrinfo* res = nullptr;
|
||||
std::string portstr = std::to_string(target.port);
|
||||
if (getaddrinfo(target.host.c_str(), portstr.c_str(), &hints, &res) != 0) return INVALID_SOCKET;
|
||||
|
||||
SOCKET s = INVALID_SOCKET;
|
||||
for (addrinfo* p = res; p != nullptr; p = p->ai_next) {
|
||||
s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
|
||||
if (s == INVALID_SOCKET) continue;
|
||||
if (connect(s, p->ai_addr, static_cast<int>(p->ai_addrlen)) == 0) break;
|
||||
closesocket(s);
|
||||
s = INVALID_SOCKET;
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void send_socks5_ok(SOCKET client) {
|
||||
uint8_t reply[10] = { 0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0 };
|
||||
send(client, reinterpret_cast<char*>(reply), sizeof(reply), 0);
|
||||
}
|
||||
|
||||
static void send_socks5_fail(SOCKET client) {
|
||||
uint8_t reply[10] = { 0x05, 0x01, 0x00, 0x01, 0, 0, 0, 0, 0, 0 };
|
||||
send(client, reinterpret_cast<char*>(reply), sizeof(reply), 0);
|
||||
}
|
||||
|
||||
static void handle_client(SOCKET client) {
|
||||
Socks5Target target;
|
||||
if (!socks5_handshake(client, target)) {
|
||||
close_sock(client);
|
||||
return;
|
||||
}
|
||||
|
||||
SOCKET upstream = connect_target(target);
|
||||
if (upstream == INVALID_SOCKET) {
|
||||
send_socks5_fail(client);
|
||||
close_sock(client);
|
||||
return;
|
||||
}
|
||||
|
||||
send_socks5_ok(client);
|
||||
std::cout << "-> " << target.host << ":" << target.port << std::endl;
|
||||
|
||||
std::vector<uint8_t> first(4096);
|
||||
int n = recv(client, reinterpret_cast<char*>(first.data()), static_cast<int>(first.size()), 0);
|
||||
if (n > 0) {
|
||||
size_t split = find_sni_split_point(first.data(), static_cast<size_t>(n));
|
||||
if (split > 0 && split < static_cast<size_t>(n)) {
|
||||
send_all(upstream, first.data(), split);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
||||
send_all(upstream, first.data() + split, static_cast<size_t>(n) - split);
|
||||
} else if (n > 0) {
|
||||
send_all(upstream, first.data(), static_cast<size_t>(n));
|
||||
}
|
||||
}
|
||||
|
||||
std::thread t1(relay, client, upstream);
|
||||
std::thread t2(relay, upstream, client);
|
||||
t1.join();
|
||||
t2.join();
|
||||
|
||||
close_sock(client);
|
||||
close_sock(upstream);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
int port = 1080;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
if (arg == "--port" && i + 1 < argc) {
|
||||
port = std::stoi(argv[++i]);
|
||||
}
|
||||
}
|
||||
|
||||
WSADATA wsa;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
|
||||
std::cerr << "WSAStartup failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
SOCKET listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (listener == INVALID_SOCKET) {
|
||||
std::cerr << "socket() failed" << std::endl;
|
||||
WSACleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
|
||||
addr.sin_port = htons(static_cast<uint16_t>(port));
|
||||
|
||||
if (bind(listener, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == SOCKET_ERROR) {
|
||||
std::cerr << "bind() failed on port " << port << std::endl;
|
||||
WSACleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (listen(listener, SOMAXCONN) == SOCKET_ERROR) {
|
||||
std::cerr << "listen() failed" << std::endl;
|
||||
WSACleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "socks5 proxy on 127.0.0.1:" << port << std::endl;
|
||||
|
||||
while (true) {
|
||||
sockaddr_in client_addr{};
|
||||
int client_len = sizeof(client_addr);
|
||||
SOCKET client = accept(listener, reinterpret_cast<sockaddr*>(&client_addr), &client_len);
|
||||
if (client == INVALID_SOCKET) continue;
|
||||
std::thread(handle_client, client).detach();
|
||||
}
|
||||
|
||||
closesocket(listener);
|
||||
WSACleanup();
|
||||
return 0;
|
||||
}
|
||||
66
src/sni_split.cpp
Normal file
66
src/sni_split.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "sni_split.h"
|
||||
|
||||
static bool read_u16(const uint8_t* d, size_t len, size_t pos, uint16_t& out) {
|
||||
if (pos + 2 > len) return false;
|
||||
out = (uint16_t(d[pos]) << 8) | d[pos + 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t find_sni_split_point(const uint8_t* data, size_t len) {
|
||||
if (len < 6 || data[0] != 0x16) return 0;
|
||||
|
||||
size_t pos = 5;
|
||||
if (pos >= len || data[pos] != 0x01) return 0;
|
||||
pos += 4;
|
||||
|
||||
if (pos + 2 + 32 > len) return 0;
|
||||
pos += 2 + 32;
|
||||
|
||||
if (pos >= len) return 0;
|
||||
uint8_t session_id_len = data[pos];
|
||||
pos += 1 + session_id_len;
|
||||
|
||||
uint16_t cipher_len;
|
||||
if (!read_u16(data, len, pos, cipher_len)) return 0;
|
||||
pos += 2 + cipher_len;
|
||||
|
||||
if (pos >= len) return 0;
|
||||
uint8_t comp_len = data[pos];
|
||||
pos += 1 + comp_len;
|
||||
|
||||
uint16_t ext_total_len;
|
||||
if (!read_u16(data, len, pos, ext_total_len)) return 0;
|
||||
pos += 2;
|
||||
|
||||
size_t ext_end = pos + ext_total_len;
|
||||
if (ext_end > len) ext_end = len;
|
||||
|
||||
while (pos + 4 <= ext_end) {
|
||||
uint16_t ext_type, ext_len;
|
||||
if (!read_u16(data, len, pos, ext_type)) break;
|
||||
if (!read_u16(data, len, pos + 2, ext_len)) break;
|
||||
size_t ext_data_start = pos + 4;
|
||||
|
||||
if (ext_type == 0x0000) {
|
||||
size_t p = ext_data_start;
|
||||
uint16_t list_len;
|
||||
if (!read_u16(data, len, p, list_len)) return 0;
|
||||
p += 2;
|
||||
if (p + 3 > len) return 0;
|
||||
|
||||
uint8_t name_type = data[p];
|
||||
uint16_t name_len;
|
||||
if (name_type != 0 || !read_u16(data, len, p + 1, name_len)) return 0;
|
||||
|
||||
size_t name_start = p + 3;
|
||||
size_t name_end = name_start + name_len;
|
||||
if (name_end > len || name_len == 0) return 0;
|
||||
|
||||
return name_start + (name_len / 2);
|
||||
}
|
||||
|
||||
pos = ext_data_start + ext_len;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
5
src/sni_split.h
Normal file
5
src/sni_split.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
size_t find_sni_split_point(const uint8_t* data, size_t len);
|
||||
Loading…
Add table
Add a link
Reference in a new issue