Socks5 协议

SOCKS 是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间代理传递 。SOCKS是 “SOCKetS” 的缩写。目前其最新的版本是5,相比于前一个版本,其新增了 IPv6, UDP验证
根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间。

协议详情

一个 socks5 连接的过程主要有两部分,协商与请求,如果协商后需要验证,则还包含验证的部分。

(1) 认证方法协商

客户端向服务端发出连接认证方法协商请求

  • VER 是 SOCKS 的版本,此处是 0x05

  • NMETHODS 是 METHODS 部分数据的长度,即客户端支持的认证方式的 count

  • METHODS 是客户端支持的认证方式列表,每个方法占一个字节

    • 0x00 不需要认证

    • 0x01 GSSAPI

    • 0x02 用户名密码认证

    • 0x03 - 0x7F 由 IANA分配(保留)

    • 0x80 - 0xFE 私人方法(保留)

    • 0xFF 无可接受的方法

服务器回应协商,从客户端提供的方法中选择一个并通知客户端:

  • VER 是SOCKS的版本号,此处应为 0x05

  • METHOD 是服务端选中的方法。如果不支持客户端提供的所有方法,则返回 0xFF

(2) 认证

如果双方同意使用用户名密码方式认证,则认证过程如下
客户端发送用户名密码:

  • AVER 认证版本号,目前为 0x01
    服务端回应认证:

  • AVER 同上

  • STATE 认证状态

    • 0x00 成功

    • 0x01 失败

(3) 请求

  • VER 为版本号,此处为 0x05

  • CMD 是 SOCK 的命令码

    • 0x01 表示 CONNECT 请求

    • 0x02 表示 BIND 请求

    • 0x03 表示 UDP 转发

  • RSV 保留,为 0x00

  • ATYP 为目标地址( DST.ADDR)的类型

    • 0x01IPV4, 目标地址部分长 4 个字节

    • 0x03域名 , 目标地址部分的第一个字节为域名长度,其余部分为域名内容(没有 \0 结尾)

    • 0x04IPV6 , 目标地址分部长 16 个字节

  • DST.ADDR 目标地址

  • DST.PORT 目标端口(网络字节序)

服务端回应客户端的请求:

  • VER 是SOCKS版本,这里应该是 0x05

  • REP 应答字段

    • 0x00 表示成功

    • 0x01 普通SOCKS服务器连接失败

    • 0x02 现有规则不允许连接

    • 0x03 网络不可达

    • 0x04 主机不可达

    • 0x05 连接被拒

    • 0x06 TTL超时

    • 0x07 不支持的命令

    • 0x08 不支持的地址类型

    • 0x09 - 0xFF 未定义

  • RSV 0x00,保留

  • ATYP BND.ADDR类型

    • 0x01 IPv4地址,DST.ADDR部分4字节长度

    • 0x03 域名,DST.ADDR部分第一个字节为域名长度,DST.ADDR剩余的内容为域名,没有\0结尾。

    • 0x04 IPv6地址,16个字节长度。

  • BND.ADDR 服务器绑定的地址

  • BND.PORT 网络字节序表示的服务器绑定的端口

实践

通过 python 建立一个 socks5 服务来分析其协议

代码如下,在本地起一个服务监听 1088端口, 接收 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import socket

def print_dump(prefix, data):
print '---',prefix,'length =',len(data),'---'
print 'orign:\n',data
while len(data) > 16:
for i in range(0,16):
print '%02X'%(ord(data[i])),
print ''
data = data[16:]
if len(data) > 0:
for c in data:
print '%02X'%(ord(c)),
print ''
print '==',prefix,'end =='

# ServerResponse
buf_0 = '\x05\x00'
buf_1 = '\x05\x00\x00\x01\xDB\xEE\x14\x58\x00\x50'
buf_2 = u'''HTTP/1.1 200 OK\r\nServer:FakeServer\r\nContent-Type: text/html\r\n\r\n<html><body>Sock5 Test</body></html>\r\n\r\n'''

STEP = 0

if __name__ == '__main__':
S_IP = '127.0.0.1'
S_PORT = 1088
BUF_SIZE = 1024

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((S_IP, S_PORT))
s.listen(1)

conn, addr = s.accept()
while 1:
data = conn.recv(BUF_SIZE)
if not data:
print 'no data'
break
print_dump('step:' + str(STEP) + ' Client',data)
if STEP == 0:
conn.send(buf_0)
print_dump('step:' + str(STEP) + ' Server',buf_0)
STEP = 1
elif STEP == 1:
conn.send(buf_1)
print_dump('step:' + str(STEP) + ' Server',buf_1)
STEP = 2
elif STEP == 2:
conn.send(buf_2)
print_dump('step:' + str(STEP) + ' Server',buf_2)
STEP = 3
break
conn.close()

使用浏览器设置 socks5 代理(这里使用firfox):

然后在访问一个 Http 网站,这里选择 http://www.runoob.com/ 来实验,可以看到服务端的输出:

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
74
75
76
77
78
79
80
81
82
83
84
--- step:0 Client length = 3 ---
orign:
║╔
05 01 00
== step:0 Client end ==
--- step:0 Server length = 2 ---
orign:

05 00
== step:0 Server end ==
--- step:1 Client length = 10 ---
orign:
║╔ ╔垲S P
05 01 00 01 DB EE 14 53 00 50
== step:1 Client end ==
--- step:1 Server length = 10 ---
orign:
║ ╔垲X P
05 00 00 01 DB EE 14 58 00 50
== step:1 Server end ==
--- step:2 Client length = 544 ---
orign:
GET / HTTP/1.1
Host: www.runoob.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Cookie: Hm_lvt_8e2a116daf0104a78d601f40a45c75b4=1530174275,1531292949,1531448092,1531795300; Hm_lvt_3eec0b7da6548cf07db3bc477ea905ee=1532919133,1533018283,1533103754
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1

47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A
48 6F 73 74 3A 20 77 77 77 2E 72 75 6E 6F 6F 62
2E 63 6F 6D 0D 0A 55 73 65 72 2D 41 67 65 6E 74
3A 20 4D 6F 7A 69 6C 6C 61 2F 35 2E 30 20 28 57
69 6E 64 6F 77 73 20 4E 54 20 31 30 2E 30 3B 20
57 69 6E 36 34 3B 20 78 36 34 3B 20 72 76 3A 36
32 2E 30 29 20 47 65 63 6B 6F 2F 32 30 31 30 30
31 30 31 20 46 69 72 65 66 6F 78 2F 36 32 2E 30
0D 0A 41 63 63 65 70 74 3A 20 74 65 78 74 2F 68
74 6D 6C 2C 61 70 70 6C 69 63 61 74 69 6F 6E 2F
78 68 74 6D 6C 2B 78 6D 6C 2C 61 70 70 6C 69 63
61 74 69 6F 6E 2F 78 6D 6C 3B 71 3D 30 2E 39 2C
2A 2F 2A 3B 71 3D 30 2E 38 0D 0A 41 63 63 65 70
74 2D 4C 61 6E 67 75 61 67 65 3A 20 7A 68 2D 43
4E 2C 7A 68 3B 71 3D 30 2E 38 2C 7A 68 2D 54 57
3B 71 3D 30 2E 37 2C 7A 68 2D 48 4B 3B 71 3D 30
2E 35 2C 65 6E 2D 55 53 3B 71 3D 30 2E 33 2C 65
6E 3B 71 3D 30 2E 32 0D 0A 41 63 63 65 70 74 2D
45 6E 63 6F 64 69 6E 67 3A 20 67 7A 69 70 2C 20
64 65 66 6C 61 74 65 0D 0A 43 6F 6F 6B 69 65 3A
20 48 6D 5F 6C 76 74 5F 38 65 32 61 31 31 36 64
61 66 30 31 30 34 61 37 38 64 36 30 31 66 34 30
61 34 35 63 37 35 62 34 3D 31 35 33 30 31 37 34
32 37 35 2C 31 35 33 31 32 39 32 39 34 39 2C 31
35 33 31 34 34 38 30 39 32 2C 31 35 33 31 37 39
35 33 30 30 3B 20 48 6D 5F 6C 76 74 5F 33 65 65
63 30 62 37 64 61 36 35 34 38 63 66 30 37 64 62
33 62 63 34 37 37 65 61 39 30 35 65 65 3D 31 35
33 32 39 31 39 31 33 33 2C 31 35 33 33 30 31 38
32 38 33 2C 31 35 33 33 31 30 33 37 35 34 0D 0A
44 4E 54 3A 20 31 0D 0A 43 6F 6E 6E 65 63 74 69
6F 6E 3A 20 6B 65 65 70 2D 61 6C 69 76 65 0D 0A
55 70 67 72 61 64 65 2D 49 6E 73 65 63 75 72 65
2D 52 65 71 75 65 73 74 73 3A 20 31 0D 0A 0D 0A
== step:2 Client end ==
--- step:2 Server length = 103 ---
orign:
HTTP/1.1 200 OK
Server:FakeServer
Content-Type: text/html

<html><body>Sock5 Test</body></html>

48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D
0A 53 65 72 76 65 72 3A 46 61 6B 65 53 65 72 76
65 72 0D 0A 43 6F 6E 74 65 6E 74 2D 54 79 70 65
3A 20 74 65 78 74 2F 68 74 6D 6C 0D 0A 0D 0A 3C
68 74 6D 6C 3E 3C 62 6F 64 79 3E 53 6F 63 6B 35
20 54 65 73 74 3C 2F 62 6F 64 79 3E 3C 2F 68 74
6D 6C 3E 0D 0A 0D 0A
== step:2 Server end ==

可以看出整个过程如下:

  1. STEP0 : Client 发起Socks5 请求,0x05 0x01 0x00

  2. Server 响应请求,选择验证方法, 0x05 0x00

  3. STEP1 : Client 发起连接请求: 0x05 0x01 0x00 0x01 0xDB 0xEE 0x14 0x53 0x00 0x50

  4. Server 给出连接成功的响应 0x05 0x00 0x00 0x01 0xDB 0xEE 0x14 0x58 0x00 0x50

  5. STEP2: Client 向 Server 发送了HTTP请求

  6. Server 给出HTTP 响应(实际是Server向实际IP转发请求并给响应转发给Client, 这里做了假的代理)

最终浏览器得到的结果如下图: