socks 代理工具 c++ 💦
推荐与参考文章、项目
linux网络编程系列(二)-socket套接字基本概念详解 - 知乎 (zhihu.com)
RFC 1928 - SOCKS 5 协议中文文档「译」 - 光韵流转 (quarkay.com)
用c语言写一个socks5代理服务器 | 木枣粽子 | BLOG (muzaozong.com)
Socks协议 (qq.com)
Client
开始监听本地端口,并创建socket文件 clientSock
accept 接受本地请求并产生socket文件 usersock,用于链接本地请求
- handleUserRequest(config,userSock)
产生serversock(链接远程服务器端),并connect服务端socket
这么来看,Client只是做了代理转发,虽然socks中已经包含代理转发,但我们可以自定义Client,比如流量加密。
Server
create listeningSocket
clientSock = accept,链接远程客户端
这里需要注意的是协议版本与认证方法数据包格式中的1 to 255
指的是从1字节到255字节,原作者这边的长度设置错误
1 2 3 4 5
| +----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 | +----+----------+----------+
|
对应socks5中的协议版本与认证方法数据包格式,进行确认。
若Ver = 5,Method = 0
,返回method=0
的response
注意的是,原项目使用IPv4时,可能会有BUG,可以修改server.cpp
中的createsocksconnnect
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| if (request.atyp == 0x01) { in_addr_t ip; memcpy(&ip, request.dstAddr, 4); char str[32];
remoteAddr.sin_addr.s_addr = ip; } else if (request.atyp == 0x03) { struct hostent *dstHost = gethostbyname(Byte_arrayToStr(request.dstAddr, request.addrLength)); char str[32];
remoteAddr.sin_addr.s_addr = *(in_addr_t *)dstHost->h_addr; } else { return -1; }
|
forwardData(ClientSock,remoteSock,0)
先获取ClientSock,再发送给remoteSock
forwardData(remoteSock,clientSock,0)
先获取remoteSock,再发送ClientSock.
可以看出,对socks5协议的认证和处理,在成功之后就代理转发socks5数据
设计要求
socks 代理工具具有以下功能:
- 实现 socks 代理,基于原作者项目
- 用户登录,基于rfc 1929
- socks 流量加密,利用Client端和Server链接
程序设计
Socks 流程 [转载]
Sokcs流程如下图 : 来源 Socks协议 (qq.com)
用户登录
参考:
rfc 1929
main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| int main(int argc, char *argv[]) { struct Config config; int opt; if (argc == 1){ printf("Usage:\nClient: -P [local port] -c -h [remote ip] -p [remote port]\nServer: -P [local port] -s -u [username] -w [password]\n"); } opterr = 0; while ((opt = getopt(argc, argv, "P:csh:u:w:p:")) != EOF) { switch (opt) { case 'P': config.localPort = atoi(optarg); break; case 'c': config.client = 1; break; case 's': config.server = 1; break; case 'h': config.serverHost = optarg; break; case 'p': config.serverPort = atoi(optarg); break; case 'u': config.username = optarg; break; case 'w': config.password = optarg; break; } } signal(SIGCHLD, SIG_IGN);
if (config.client) { startClient(config); } else if (config.server) { printf("Username: %s\nPassword: %s\n",config.username,config.password); startServer(config); } printf("Finish\n"); return 0; }
|
Config
添加key value
1 2 3 4 5 6 7 8 9
| struct Config { int localPort; int client =0; int server =0; char* serverHost; int serverPort; char* username; char* password; };
|
server
修改validateSock5Connection
函数,添加AuthSock5Connection
函数用于身份验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| int AuthSock5Connection(struct Config config,int clientSock){ printf("Auth sock5 connection.\n"); char buffer[SOCK5_AUTHENTICATION_REQUEST_MAX_LENGTH]; if (retryRecv(clientSock, buffer, SOCK5_AUTHENTICATION_REQUEST_MAX_LENGTH) < 0) { return -1; } int ok = 0; struct Sock5AuthRequest request = Sock5AuthRequest_read((Byte *)buffer);
if( strcmp((char *) request.username ,config.username) == 0 && strcmp((char *) request.password ,config.password) == 0 && request.version == 0x01 ){ ok = 1; } else{ if( strcmp((char *) request.username ,config.username) != 0 ){ printf("Error: username\n"); printf("Request: %s\n",(char *) request.username); printf("config: %s\n",config.username); } if( strcmp((char *) request.password ,config.password) != 0 ){ printf("Error: password\n"); printf("Request: %s\n",(char *) request.password); printf("config: %s\n",config.password); } if( request.version != 0x01){ printf("Error: Version\n"); } } struct Sock5AuthResponse response; response.version = 0x01; response.Status = ok ? (Byte)0x00 : (Byte)0xFF; return (int)retrySend(clientSock, Sock5AuthResponse_toString(response), 2); }
int validateSock5Connection(struct Config config,int clientSock) { printf("validate sock5 connection.\n"); char buffer[SOCK5_VALIDATE_REQUEST_MAX_LENGTH]; if (retryRecv(clientSock, buffer, SOCK5_VALIDATE_REQUEST_MAX_LENGTH) < 0) { return -1; }
struct Sock5ValidateRequest request = Sock5ValidateRequest_read((Byte *)buffer);
int ok = 1;
ok &= request.version == 0x05;
int allowNoAuth = 0; int allowAuth =0; for (int i = 0; i < request.methodNum; i++) { if (request.methods[i] == 0x02 ){ allowAuth = 1 ; printf("Method: USERNAME/PASSWORD\n"); } if (request.methods[i] == 0x00) { allowNoAuth = 1; printf("Method: NoAuth\n"); } } struct Sock5ValidateResponse response; response.version = 0x05; if(ok && allowAuth){ response.method = (Byte)0x02 ; retrySend(clientSock, Sock5ValidateResponse_toString(response), 2); return (int)AuthSock5Connection(config,clientSock); }else{ response.method = (ok && allowNoAuth) ? (Byte)0x00 : (Byte)0xFF; return (int)retrySend(clientSock, Sock5ValidateResponse_toString(response), 2); } }
|
socks5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #socks5.h struct Sock5AuthRequest{ Byte version; Byte usernameLen; Byte *username; Byte passwordLen; Byte *password; };
struct Sock5AuthResponse{ Byte version; Byte Status; };
#socks5.cpp struct Sock5AuthRequest Sock5AuthRequest_read(Byte *buffer){ struct Sock5AuthRequest request; request.version = buffer[0]; request.usernameLen = buffer[1]; request.username = (Byte *)malloc(request.usernameLen); Byte_copyN(request.username, buffer + 2, request.usernameLen); request.passwordLen = buffer[2+request.usernameLen]; request.password = (Byte *)malloc(request.passwordLen); Byte_copyN(request.password, buffer + 3 + request.usernameLen, request.passwordLen); return request; }
char* Sock5AuthResponse_toString(struct Sock5AuthResponse response){ Byte *result = (Byte *)malloc(3); int p = 0; result[p++] = response.version; result[p] = response.Status; return (char *)result; }
|
实现效果
设置/etc/proxychains4.conf
启动服务
启动代理
socks 流量加密
大致思路,在客户端和服务端间添加流量加密。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include "aes/aes.hpp"
void main(){ void encryptData(uint8_t * buffer,ssize_t n){ struct AES_ctx ctx;
uint8_t key[] = "lxr@xzas@#$%WERT"; uint8_t iv[] = "xzas@lxr@WSX3edc"; AES_init_ctx_iv(&ctx,key,iv); AES_CTR_xcrypt_buffer(&ctx,buffer,n);
}
void forwardData(int srcSock, int dstSock, int encryption) { char buffer[8192]; ssize_t n; while ((n = retryRecv(srcSock, buffer, 8000)) > 0) { if(encryption == 1){ encryptData((uint8_t *)buffer,n); } if (retrySend(dstSock, buffer, (size_t)n) < 0) { break; } } shutdown(srcSock, SHUT_RDWR); shutdown(dstSock, SHUT_RDWR); }
}
|
其中#include "aes/aes.hpp"
是基于** tiny-AES-c**
仔细观察,可以发现流量在认证部分,还是比较明显的,但完全可以添加垃圾数据,大致思路就是在后面放无用的数据。为什么无用,因为在检验认证过程中,采取的方法是读取固定长度的buffer,不太可能去误读取垃圾数据段。
还存在的问题
当Client
端强制关闭时,socket 暂时无法使用。还有一个安全上的问题,流量还未混淆,以及可以被重放攻击。
总结
感谢用c语言写一个socks5代理服务器 | 木枣粽子 | BLOG (muzaozong.com)前辈的分享和iyzyi
的帮助。
通过这个项目,个人算是比较详细的学习了SOCKS5协议与其编写,以及明白C++变量的复杂(不同环境下,字节还可能不一样,要使用规范的模式)。
应对重放攻击,本来想在socks协议上添加上一个挑战与响应的机制,但不太好实现,要重新写一个类似proxychains
的工具了。