socks5协议
原理
socks5 协议简介
实现SOCKS5协议 | 鱼儿的博客
socks是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。根据OSI七层模型来划分,SOCKS属于会话层协议,位于表示层与传输层之间。主要流程如下图所示 ,参考:
Writing a simple SOCKS server in Python | Artem Golubin
Python编写socks5服务器 | HatBoy的个人主页
SOCKS协议第五版(SOCKS Protocol Version 5) - 作业部落 Cmd Markdown 编辑阅读器
https://www.ietf.org/rfc/rfc1928.txt
https://www.ietf.org/rfc/rfc1929.txt
1.协商
version | nmethods | methods | ---------------------> | version | method |
---|---|---|---|---|---|
1 byte | 1 byte | 0 to 255 bytes | <-------------------- | 1 byte | 1 byte |
Here version
field represents a version of the protocol, which equals to 5 in our case.
The methods
field consists of a sequence of supported methods by the client.
Thus the nmethods
field indicates the length of a methods
sequence.
2.认证
version | ulen | uname | plen | passwd | ------------> | version | status |
---|---|---|---|---|---|---|---|
1 byte | 1 byte | 0 to 255 bytes | 1 byte | 0 to 255 bytes | <----------- | 1 byte | 1 byte |
The ulen
and plen
fields represent lengths of text fields so the server knows how much data it should read from the client.
The status
field of 0 indicates a successful authorization, while other values treated as a failure.
3.请求
version | cmd | rsv | atyp | dst.addr | dst.port |
---|---|---|---|---|---|
1 byte | 1 byte | 1 byte | 1 byte | 4 to 255 bytes | 2 bytes |
The cmd
field indicates the type of connection. This article is limited to CONNECT
method only, which is used for TCP connections.
RSV
RESERVED
ATYP address type of following address
DST.ADDR desired destination address
DST.PORT desired destination port in network octet order
version | rep | rsv | atyp | bnd.addr | bnd.port |
---|---|---|---|---|---|
1 byte | 1 byte | 1 byte | 1 byte | 4 to 255 bytes | 2 bytes |
REP Reply field
BND.ADDR server bound address
BND.PORT server bound port in network octet order
bnd
就是代理起的一个中介socket。
4.数据转发
略,可参考上述文章。
编程
Writing a simple SOCKS server in Python | Artem Golubin
Python编写socks5服务器 | HatBoy的个人主页
1.!
表示大端
struct — 将字节串解读为打包的二进制数据 — Python 3.9.6 文档
2.send和sendall的区别
全网最详细python中socket套接字send与sendall的区别 - CoderEllison - 博客园
3.class socketserver.ThreadingMixIn
Forking and threading versions of each type of server can be created using these mix-in classes.
socket.getsockname
() 返回套接字本身的地址
参考代码如下(更详细的参考上述文章):
# 含有认证
import logging
import select
import socket
import struct
from socketserver import ThreadingMixIn, TCPServer, StreamRequestHandler #3.
logging.basicConfig(level=logging.DEBUG)
SOCKS_VERSION = 5
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
pass
class SocksProxy(StreamRequestHandler):
username = 'username'
password = 'password'
def handle(self):
logging.info('Accepting connection from %s:%s' % self.client_address)
# greeting header
# read and unpack 2 bytes from a client
header = self.connection.recv(2)
version, nmethods = struct.unpack("!BB", header) # 1.!大端,BB unsigned char 1个字节
# socks 5
assert version == SOCKS_VERSION
assert nmethods > 0
# get available methods
methods = self.get_available_methods(nmethods)
# accept only USERNAME/PASSWORD auth
if 2 not in set(methods):
# close connection
self.server.close_request(self.request)
return
# send welcome message
self.connection.sendall(struct.pack("!BB", SOCKS_VERSION, 2))
if not self.verify_credentials():
return
# request
version, cmd, _, address_type = struct.unpack("!BBBB", self.connection.recv(4))
assert version == SOCKS_VERSION
if address_type == 1: # IPv4
address = socket.inet_ntoa(self.connection.recv(4))
elif address_type == 3: # Domain name
domain_length = self.connection.recv(1)[0]
address = self.connection.recv(domain_length)
address = socket.gethostbyname(address)
port = struct.unpack('!H', self.connection.recv(2))[0]
# reply
try:
if cmd == 1: # CONNECT
remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote.connect((address, port))
bind_address = remote.getsockname()
logging.info('Connected to %s %s' % (address, port))
else:
self.server.close_request(self.request)
addr = struct.unpack("!I", socket.inet_aton(bind_address[0]))[0]
port = bind_address[1]
reply = struct.pack("!BBBBIH", SOCKS_VERSION, 0, 0, 1,
addr, port)
except Exception as err:
logging.error(err)
# return connection refused error
reply = self.generate_failed_reply(address_type, 5)
self.connection.sendall(reply)
# establish data exchange
if reply[1] == 0 and cmd == 1:
self.exchange_loop(self.connection, remote)
self.server.close_request(self.request)
def get_available_methods(self, n):
methods = []
for i in range(n):
methods.append(ord(self.connection.recv(1)))
return methods
def verify_credentials(self):
version = ord(self.connection.recv(1))
assert version == 1
username_len = ord(self.connection.recv(1))
username = self.connection.recv(username_len).decode('utf-8')
password_len = ord(self.connection.recv(1))
password = self.connection.recv(password_len).decode('utf-8')
if username == self.username and password == self.password:
# success, status = 0
response = struct.pack("!BB", version, 0)
self.connection.sendall(response)
return True
# failure, status != 0
response = struct.pack("!BB", version, 0xFF)
self.connection.sendall(response) # 2.send的升级版本 全部发送
self.server.close_request(self.request)
return False
def generate_failed_reply(self, address_type, error_number):
return struct.pack("!BBBBIH", SOCKS_VERSION, error_number, 0, address_type, 0, 0)
def exchange_loop(self, client, remote):
while True:
# wait until client or remote is available for read
r, w, e = select.select([client, remote], [], [])
if client in r:
data = client.recv(4096)
if remote.send(data) <= 0:
break
if remote in r:
data = remote.recv(4096)
if client.send(data) <= 0:
break
if __name__ == '__main__':
with ThreadingTCPServer(('192.168.10.1', 9011), SocksProxy) as server: # 修改为代理ip和端口
server.serve_forever()
实验
代理:192.168.10.1
socks5客户端:192.168.10.129
客户端欲访问的服务器:192.168.10.128
1.代理直接运行上述代码即可,注意修改下main里的ip,为代理的ip,这里是192.168.10.1。
2.客户端可以用linux的proxychains,如果使用认证的话,就必须在/ect/proxychains.conf最后加上username和passward,不认证的话,就不用username和password。如下图所示,其中,192.168.10.1是代理服务器,间隔是tab键。
socks5 192.168.10.1 9011 username password
3.服务器(192.168.10.128)可以用python起个web服务(也可以就是用www.baidu.com等),如果是python2,使用的命令是:
python -m SimpleHTTPServer 8088
如果是python3,使用的命令是:
python -m http.server 8088
抓包分析
使用wireshark,过滤条件。
(ip.src==192.168.10.1 || ip.dst==192.168.10.1) and tcp
前面的协商,认证,请求都是在tcp data放的内容。这是第一条。05是version,02是两种方法,00是不认证,02是username,password。
其它依次分析即可,到数据转发,就是在已经获得的remote_addr和port基础上。把客户端的http数据加上remote_addr和port作为tcp的ip和port转发,然后和remote联系。后面就没有tcp data了。然后通过代理转发即可。