New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

你也能写个 Shadowsocks #12

Open
gwuhaolin opened this Issue Nov 3, 2017 · 42 comments

Comments

Projects
None yet
@gwuhaolin
Owner

gwuhaolin commented Nov 3, 2017

本文将教你从0写一个Shadowsocks,无需任何基础,读完本文你就能完成一个轻量级、高性能的 Shadowsocks 代替品。

我们暂且把最终完成的项目叫做 Lightsocks,如果你很急切地想看到结果,可以先体验本文最终完成的项目 Lightsocks ,也可以下载阅读源码。

认识 Shadowsocks

Shadowsocks 是一个能骗过防火墙的网络代理工具。它把要传输的原数据经过加密后再传输,网络中的防火墙由于得不出要传输的原内容是什么而只好放行,于是就完成了防火墙穿透,也即是所谓的“翻墙”。

在自由的网络环境下,在本机上访问服务时是直接和远程服务建立连接传输数据,流程如图:
自由网络环境下的传输流程

但在受限的网络环境下会有防火墙,本机电脑和远程服务之间传输的数据都必须通过防火墙的检查,流程如图:
受限网络环境下的传输流程
如果防火墙发现你在传输受限的内容,就把拦截本次传输,就会导致在本机无法访问远程服务。

而 Shadowsocks 所做的就是把传输的数据加密,防火墙得到的数据是加密后的数据,防火墙不知道传输的原内容是什么,于是防火墙就放行本次请求,于是在本机就访问到了远程服务,流程如图:
shadowsocks下的传输流程

也就是说使用 Shadowsocks 的前提是:

  • 一台在防火墙之外的服务器;
  • 在本机需要安装 Shadowsocks 本地端,用于加密传输数据;
  • 服务器需要安装 Shadowsocks 服务端,用于解密加密后的传输数据,解密出原数据后发送到目标服务器。

Shadowsocks 原理

Shadowsocks 由两部分组成,运行在本地的 ss-local 和运行在防火墙之外服务器上的 ss-server,下面来分别详细介绍它们的职责(以下对 Shadowsocks 原理的解析只是我的大概估计,可能会有细微的差别)。

ss-local

ss-local 的职责是在本机启动和监听着一个服务,本地软件的网络请求都先发送到 ss-local,ss-local 收到来自本地软件的网络请求后,把要传输的原数据根据用户配置的加密方法和密码进行加密,再转发到墙外的服务器去。

ss-server

ss-server 的职责是在墙外服务器启动和监听一个服务,该服务监听来自本机的 ss-local 的请求。在收到来自 ss-local 转发过来的数据时,会先根据用户配置的加密方法和密码对数据进行对称解密,以获得加密后的数据的原内容。同时还会解 SOCKS5 协议,读出本次请求真正的目标服务地址(例如 Google 服务器地址),再把解密后得到的原数据转发到真正的目标服务。

当真正的目标服务返回了数据时,ss-server 端会把返回的数据加密后转发给对应的 ss-local 端,ss-local 端收到数据再解密后,转发给本机的软件。这是一个对称相反的过程。

由于 ss-local 和 ss-server 端都需要用对称加密算法对数据进行加密和解密,因此这两端的加密方法和密码必须配置为一样。Shadowsocks 提供了一系列标准可靠的对称算法可供用户选择,例如 rc4、aes、des、chacha20 等等。Shadowsocks 对数据加密后再传输的目的是为了混淆原数据,让途中的防火墙无法得出传输的原数据。但其实用这些安全性高计算量大的对称加密算法去实现混淆有点“杀鸡用牛刀”。

SOCKS5 协议介绍

Shadowsocks 的数据传输是建立在 SOCKS5 协议之上的,SOCKS5 是 TCP/IP 层面的网络代理协议。
ss-server 端解密出来的数据就是采用 SOCKS5 协议封装的,通过 SOCKS5 协议 ss-server 端能读出本机软件想访问的服务的真正地址以及要传输的原数据,下面来详细介绍 SOCKS5 协议的通信细节。

建立连接

客户端向服务端连接连接,客户端发送的数据包如下:

VER NMETHODS METHODS
1 1 1

其中各个字段的含义如下:
-VER:代表 SOCKS 的版本,SOCKS5 默认为0x05,其固定长度为1个字节;
-NMETHODS:表示第三个字段METHODS的长度,它的长度也是1个字节;
-METHODS:表示客户端支持的验证方式,可以有多种,他的长度是1-255个字节。

目前支持的验证方式共有:

  • 0x00:NO AUTHENTICATION REQUIRED(不需要验证)
  • 0x01:GSSAPI
  • 0x02:USERNAME/PASSWORD(用户名密码)
  • 0x03: to X'7F' IANA ASSIGNED
  • 0x80: to X'FE' RESERVED FOR PRIVATE METHODS
  • 0xFF: NO ACCEPTABLE METHODS(都不支持,没法连接了)

响应连接

服务端收到客户端的验证信息之后,就要回应客户端,服务端需要客户端提供哪种验证方式的信息。服务端回应的包格式如下:

VER METHOD
1 1

其中各个字段的含义如下:

  • VER:代表 SOCKS 的版本,SOCKS5 默认为0x05,其固定长度为1个字节;
  • METHOD:代表服务端需要客户端按此验证方式提供的验证信息,其值长度为1个字节,可为上面六种验证方式之一。

举例说明,比如服务端不需要验证的话,可以这么回应客户端:

VER METHOD
0x05 0x00

和目标服务建立连接

客户端发起的连接由服务端验证通过后,客户端下一步应该告诉真正目标服务的地址给服务器,服务器得到地址后再去请求真正的目标服务。也就是说客户端需要把 Google 服务的地址google.com:80告诉服务端,服务端再去请求google.com:80
目标服务地址的格式为 (IP或域名)+端口,客户端需要发送的包格式如下:

VER CMD RSV ATYP DST.ADDR DST.PORT
1 1 0x00 1 Variable 2

各个字段的含义如下:

  • VER:代表 SOCKS 协议的版本,SOCKS 默认为0x05,其值长度为1个字节;
  • CMD:代表客户端请求的类型,值长度也是1个字节,有三种类型;
    • CONNECT0x01
    • BIND0x02
    • UDP: ASSOCIATE 0x03
  • RSV:保留字,值长度为1个字节;
  • ATYP:代表请求的远程服务器地址类型,值长度1个字节,有三种类型;
    • IPV4: address: 0x01
    • DOMAINNAME: 0x03
    • IPV6: address: 0x04
  • DST.ADDR:代表远程服务器的地址,根据 ATYP 进行解析,值长度不定;
  • DST.PORT:代表远程服务器的端口,要访问哪个端口的意思,值长度2个字节。

服务端在得到来自客户端告诉的目标服务地址后,便和目标服务进行连接,不管连接成功与否,服务器都应该把连接的结果告诉客户端。在连接成功的情况下,服务端返回的包格式如下:

VER REP RSV ATYP BND.ADDR BND.PORT
1 1 0x00 1 Variable 2

各个字段的含义如下:

  • VER:代表 SOCKS 协议的版本,SOCKS 默认为0x05,其值长度为1个字节;
  • REP代表响应状态码,值长度也是1个字节,有以下几种类型
    • 0x00 succeeded
    • 0x01 general SOCKS server failure
    • 0x02 connection not allowed by ruleset
    • 0x03 Network unreachable
    • 0x04 Host unreachable
    • 0x05 Connection refused
    • 0x06 TTL expired
    • 0x07 Command not supported
    • 0x08 Address type not supported
    • 0x09 to 0xFF unassigned
  • RSV:保留字,值长度为1个字节
  • ATYP:代表请求的远程服务器地址类型,值长度1个字节,有三种类型
    • IP V4 address: 0x01
    • DOMAINNAME: 0x03
    • IP V6 address: 0x04
  • BND.ADDR:表示绑定地址,值长度不定。
  • BND.PORT: 表示绑定端口,值长度2个字节

数据转发

客户端在收到来自服务器成功的响应后,就会开始发送数据了,服务端在收到来自客户端的数据后,会转发到目标服务。

总结

SOCKS5 协议的目的其实就是为了把来自原本应该在本机直接请求目标服务的流程,放到了服务端去代理客户端访问。
其运行流程总结如下:

  1. 本机和代理服务端协商和建立连接;
  2. 本机告诉代理服务端目标服务的地址;
  3. 代理服务端去连接目标服务,成功后告诉本机;
  4. 本机开始发送原本应发送到目标服务的数据给代理服务端,由代理服务端完成数据转发。

以上内容来自 SOCKS5 协议规范 rfc1928

Lightsocks 实现

要实现 Lightsocks 需要实现两部分:运行在本地的 lightsocks-local,和运行在墙外代理服务器上 lightsocks-server。
下面来分别教你如果使用 Golang 来实现它们,采用 Golang 语言的原因在于:性能好、跨平台、适合高并发、学习门槛低。对Golang感兴趣?请看Golang 中文学习资料汇总

实现数据混淆

在 Shadowsocks 中是采用的标准的对称加密算法去实现数据混淆的,对称算法在加密和解密过程中需要大量计算。
为了简单起见,Lightsocks 将采用最简单高效的方法去实现数据混淆,具体原理如下。

这个数据混淆算法和对称加密很相似,两端都需要有同样的密钥。
这个密钥有如下要求:

  • 由256个 byte 组成,也就是一个数组,在 Golang 中类型表示为 [256]byte
  • 这个数组必须由 0~255 这256个数字组成,一个都不能差;
  • 这个数组中第I个的值不能等于I

例如以下为一个合法的密钥(上为索引,下为值):

0 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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
186 118 82 201 235 236 180 66 228 96 43 90 203 200 34 104 41 222 165 74 240 20 244 67 114 191 220 147 196 183 229 123 208 19 127 187 84 148 56 170 133 160 202 21 53 78 59 64 120 27 167 175 39 10 4 132 89 230 152 73 221 88 141 158 251 79 225 87 14 23 68 250 199 168 218 60 40 169 75 86 153 134 83 49 128 231 217 239 226 177 57 24 234 63 7 112 166 211 254 179 157 215 227 224 233 81 172 26 122 219 48 151 232 50 108 44 0 192 65 76 109 252 248 47 154 33 209 115 31 15 45 206 247 124 77 8 182 144 1 72 131 52 245 198 238 5 188 116 55 216 155 2 178 189 162 136 243 184 58 69 70 99 36 25 35 174 195 18 205 30 190 142 210 113 145 101 97 161 100 91 242 138 93 171 98 237 212 255 80 102 119 204 107 105 111 11 29 146 129 117 135 176 163 207 103 22 246 125 150 106 126 197 249 62 51 193 32 3 110 46 85 71 159 139 12 164 95 121 140 241 253 130 173 213 54 143 16 94 9 61 156 214 28 17 37 42 181 149 185 223 92 38 13 194 6 137

如果原数据为 [5,0,1,2,3],则采用以上密钥加密后变成 [236,186,118,82,201]
如果加密后的数据为 [186,118,82,201,235],则采用以上密钥解密得到的原数据为 [0,1,2,3,4]

聪明的你肯定看懂了其中的规律:把1~255 这256个数字确定一种一对一的映射关系,加密是从一个数字得到对应的一个数字,而解密则是反向的过程,而这个密钥的作用正是描述这个映射关系。
这其实就是中学学的反函数

为什么要这样设计数据混淆算法呢?在数据传输时,数据是以 byte 为最小单位流式传输的。一个 byte 的取值只可能是 0~255。该混淆算法可以直接对一个个 byte 进行加解密,而无需像标准的对称算法那样只能对一大块数据进行加密。
再加上本算法的加解密 N byte 数据的算法复杂度为 N(直接通过数组索引访问),非常适合流式加密。

以上加密算法的安全性怎么样呢?符合以上要求的密钥匙有多少种组合呢?我们来算算:
这其实就是初中学的排列组合中的排列问题,形象点其实就是,把 0~255 个不同编号的人安排到 0~255 个不同编号的坑去,并且不能有编号一样的情况,有多少种排法。
也就是 A(255,255)=255*254*253*...*1=255!,但其中有一半为有重复的情况,
最终结果为 255!/2
其值大概为 10^500 这个数量级。

以上加密算法虽然破绽很多,但足以实现高效的数据混淆,骗过防火墙。

目前采用对称加密算法实现数据混淆的 Shadowsocks 已经能被一些防火墙通过机器学习算法通过特征分析识别出传输的原内容适合合法,而 Lightsocks 的这套混淆算法目前还不能被轻易的识别出来。

随机产生一个以上密钥匙的代码如下

package core
import (
	"math/rand"
	"time"
)
const PasswordLength = 256
type Password [PasswordLength]byte

func init() {
	// 更新随机种子,防止生成一样的随机密码
	rand.Seed(time.Now().Unix())
}

// 产生 256个byte随机组合的 密码
func RandPassword() *Password {
	// 随机生成一个由  0~255 组成的 byte 数组
	intArr := rand.Perm(PasswordLength)
	password := &Password{}
	for i, v := range intArr {
		password[i] = byte(v)
		if i == v {
			// 确保不会出现如何一个byte位出现重复
			return RandPassword()
		}
	}
	return password
}

对数据进行加密解密的代码如下

package core

type Cipher struct {
	// 编码用的密码
	encodePassword *Password
	// 解码用的密码
	decodePassword *Password
}

// 加密原数据
func (cipher *Cipher) encode(bs []byte) {
	for i, v := range bs {
		bs[i] = cipher.encodePassword[v]
	}
}

// 解码加密后的数据到原数据
func (cipher *Cipher) decode(bs []byte) {
	for i, v := range bs {
		bs[i] = cipher.decodePassword[v]
	}
}

// 新建一个编码解码器
func NewCipher(encodePassword *Password) *Cipher {
	decodePassword := &Password{}
	for i, v := range encodePassword {
		encodePassword[i] = v
		decodePassword[v] = byte(i)
	}
	return &Cipher{
		encodePassword: encodePassword,
		decodePassword: decodePassword,
	}
}

再使用以上的 Cipher 去封装一个加密传输的 SecureSocket,以方便直接加解密 TCP Socket 中的流式数据,代码如下

package core

import (
	"errors"
	"fmt"
	"io"
	"net"
)

const (
	BufSize = 1024
)

// 加密传输的 TCP Socket
type SecureSocket struct {
	Cipher     *Cipher
	ListenAddr *net.TCPAddr
	RemoteAddr *net.TCPAddr
}

// 从输入流里读取加密过的数据,解密后把原数据放到bs里
func (secureSocket *SecureSocket) DecodeRead(conn *net.TCPConn, bs []byte) (n int, err error) {
	n, err = conn.Read(bs)
	if err != nil {
		return
	}
	secureSocket.Cipher.decode(bs[:n])
	return
}

// 把放在bs里的数据加密后立即全部写入输出流
func (secureSocket *SecureSocket) EncodeWrite(conn *net.TCPConn, bs []byte) (int, error) {
	secureSocket.Cipher.encode(bs)
	return conn.Write(bs)
}

// 从src中源源不断的读取原数据加密后写入到dst,直到src中没有数据可以再读取
func (secureSocket *SecureSocket) EncodeCopy(dst *net.TCPConn, src *net.TCPConn) error {
	buf := make([]byte, BufSize)
	for {
		readCount, errRead := src.Read(buf)
		if errRead != nil {
			if errRead != io.EOF {
				return errRead
			} else {
				return nil
			}
		}
		if readCount > 0 {
			writeCount, errWrite := secureSocket.EncodeWrite(dst, buf[0:readCount])
			if errWrite != nil {
				return errWrite
			}
			if readCount != writeCount {
				return io.ErrShortWrite
			}
		}
	}
}

// 从src中源源不断的读取加密后的数据解密后写入到dst,直到src中没有数据可以再读取
func (secureSocket *SecureSocket) DecodeCopy(dst *net.TCPConn, src *net.TCPConn) error {
	buf := make([]byte, BufSize)
	for {
		readCount, errRead := secureSocket.DecodeRead(src, buf)
		if errRead != nil {
			if errRead != io.EOF {
				return errRead
			} else {
				return nil
			}
		}
		if readCount > 0 {
			writeCount, errWrite := dst.Write(buf[0:readCount])
			if errWrite != nil {
				return errWrite
			}
			if readCount != writeCount {
				return io.ErrShortWrite
			}
		}
	}
}

// 和远程的socket建立连接,他们之间的数据传输会加密
func (secureSocket *SecureSocket) DialRemote() (*net.TCPConn, error) {
	remoteConn, err := net.DialTCP("tcp", nil, secureSocket.RemoteAddr)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("连接到远程服务器 %s 失败:%s", secureSocket.RemoteAddr, err))
	}
	return remoteConn, nil
}

这个 SecureSocket 用于 local 端和 server 端之间进行 TCP 通信,并且只使用 SecureSocket 通信时中间传输的数据会被加密,防火墙无法读到原数据。

实现 local 端

运行在本机的 local 端的职责是把本机程序发送给它的数据经过加密后转发给墙外的代理服务器,总体工作流程如下:

  1. 监听来自本机浏览器的代理请求;
  2. 转发前加密数据;
  3. 转发socket数据到墙外代理服务端;
  4. 把服务端返回的数据转发给用户的浏览器。

实现以上功能的 local 端代码如下

package local

import (
	"github.com/gwuhaolin/lightsocks/core"
	"log"
	"net"
)

type LsLocal struct {
	*core.SecureSocket
}

// 新建一个本地端
func New(password *core.Password, listenAddr, remoteAddr *net.TCPAddr) *LsLocal {
	return &LsLocal{
		SecureSocket: &core.SecureSocket{
			Cipher:     core.NewCipher(password),
			ListenAddr: listenAddr,
			RemoteAddr: remoteAddr,
		},
	}
}

// 本地端启动监听,接收来自本机浏览器的连接
func (local *LsLocal) Listen(didListen func(listenAddr net.Addr)) error {
	listener, err := net.ListenTCP("tcp", local.ListenAddr)
	if err != nil {
		return err
	}

	defer listener.Close()

	if didListen != nil {
		didListen(listener.Addr())
	}

	for {
		userConn, err := listener.AcceptTCP()
		if err != nil {
			log.Println(err)
			continue
		}
		// userConn被关闭时直接清除所有数据 不管没有发送的数据
		userConn.SetLinger(0)
		go local.handleConn(userConn)
	}
	return nil
}

func (local *LsLocal) handleConn(userConn *net.TCPConn) {
	defer userConn.Close()

	proxyServer, err := local.DialRemote()
	if err != nil {
		log.Println(err)
		return
	}
	defer proxyServer.Close()
	// Conn被关闭时直接清除所有数据 不管没有发送的数据
	proxyServer.SetLinger(0)

	// 进行转发
	// 从 proxyServer 读取数据发送到 localUser
	go func() {
		err := local.DecodeCopy(userConn, proxyServer)
		if err != nil {
			// 在 copy 的过程中可能会存在网络超时等 error 被 return,只要有一个发生了错误就退出本次工作
			userConn.Close()
			proxyServer.Close()
		}
	}()
	// 从 localUser 发送数据发送到 proxyServer,这里因为处在翻墙阶段出现网络错误的概率更大
	local.EncodeCopy(proxyServer, userConn)
}

实现 server 端

运行在墙外代理服务器的 server 端职责如下:

  1. 监听来自本地代理客户端的请求;
  2. 解密本地代理客户端请求的数据,解析 SOCKS5 协议,连接用户浏览器真正想要连接的远程服务器;
  3. 转发用户浏览器真正想要连接的远程服务器返回的数据的加密后的内容到本地代理客户端。

实现以上功能的代码如下

package server

import (
	"encoding/binary"
	"github.com/gwuhaolin/lightsocks/core"
	"log"
	"net"
)

type LsServer struct {
	*core.SecureSocket
}

// 新建一个服务端
func New(password *core.Password, listenAddr *net.TCPAddr) *LsServer {
	return &LsServer{
		SecureSocket: &core.SecureSocket{
			Cipher:     core.NewCipher(password),
			ListenAddr: listenAddr,
		},
	}
}

// 运行服务端并且监听来自本地代理客户端的请求
func (lsServer *LsServer) Listen(didListen func(listenAddr net.Addr)) error {
	listener, err := net.ListenTCP("tcp", lsServer.ListenAddr)
	if err != nil {
		return err
	}

	defer listener.Close()

	if didListen != nil {
		didListen(listener.Addr())
	}

	for {
		localConn, err := listener.AcceptTCP()
		if err != nil {
			log.Println(err)
			continue
		}
		// localConn被关闭时直接清除所有数据 不管没有发送的数据
		localConn.SetLinger(0)
		go lsServer.handleConn(localConn)
	}
	return nil
}

// 解 SOCKS5 协议
// https://www.ietf.org/rfc/rfc1928.txt
func (lsServer *LsServer) handleConn(localConn *net.TCPConn) {
	defer localConn.Close()
	buf := make([]byte, 256)

	/**
	   The localConn connects to the dstServer, and sends a ver
	   identifier/method selection message:
		          +----+----------+----------+
		          |VER | NMETHODS | METHODS  |
		          +----+----------+----------+
		          | 1  |    1     | 1 to 255 |
		          +----+----------+----------+
	   The VER field is set to X'05' for this ver of the protocol.  The
	   NMETHODS field contains the number of method identifier octets that
	   appear in the METHODS field.
	*/
	// 第一个字段VER代表Socks的版本,Socks5默认为0x05,其固定长度为1个字节
	_, err := lsServer.DecodeRead(localConn, buf)
	// 只支持版本5
	if err != nil || buf[0] != 0x05 {
		return
	}

	/**
	   The dstServer selects from one of the methods given in METHODS, and
	   sends a METHOD selection message:

		          +----+--------+
		          |VER | METHOD |
		          +----+--------+
		          | 1  |   1    |
		          +----+--------+
	*/
	// 不需要验证,直接验证通过
	lsServer.EncodeWrite(localConn, []byte{0x05, 0x00})

	/**
		          +----+-----+-------+------+----------+----------+
		          |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
		          +----+-----+-------+------+----------+----------+
		          | 1  |  1  | X'00' |  1   | Variable |    2     |
		          +----+-----+-------+------+----------+----------+
	*/

	// 获取真正的远程服务的地址
	n, err := lsServer.DecodeRead(localConn, buf)
	// n 最短的长度为7 情况为 ATYP=3 DST.ADDR占用1字节 值为0x0
	if err != nil || n < 7 {
		return
	}

	// CMD代表客户端请求的类型,值长度也是1个字节,有三种类型
	// CONNECT X'01'
	if buf[1] != 0x01 {
		// 目前只支持 CONNECT
		return
	}

	var dIP []byte
	// aType 代表请求的远程服务器地址类型,值长度1个字节,有三种类型
	switch buf[3] {
	case 0x01:
		//	IP V4 address: X'01'
		dIP = buf[4 : 4+net.IPv4len]
	case 0x03:
		//	DOMAINNAME: X'03'
		ipAddr, err := net.ResolveIPAddr("ip", string(buf[5:n-2]))
		if err != nil {
			return
		}
		dIP = ipAddr.IP
	case 0x04:
		//	IP V6 address: X'04'
		dIP = buf[4 : 4+net.IPv6len]
	default:
		return
	}
	dPort := buf[n-2:]
	dstAddr := &net.TCPAddr{
		IP:   dIP,
		Port: int(binary.BigEndian.Uint16(dPort)),
	}

	// 连接真正的远程服务
	dstServer, err := net.DialTCP("tcp", nil, dstAddr)
	if err != nil {
		return
	} else {
		defer dstServer.Close()
		// Conn被关闭时直接清除所有数据 不管没有发送的数据
		dstServer.SetLinger(0)

		// 响应客户端连接成功
		/**
		          +----+-----+-------+------+----------+----------+
		          |VER | REP |  RSV  | ATYP | BND.ADDR | BND.PORT |
		          +----+-----+-------+------+----------+----------+
		          | 1  |  1  | X'00' |  1   | Variable |    2     |
		          +----+-----+-------+------+----------+----------+
		*/
		// 响应客户端连接成功
		lsServer.EncodeWrite(localConn, []byte{0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
	}

	// 进行转发
	// 从 localUser 读取数据发送到 dstServer
	go func() {
		err := lsServer.DecodeCopy(dstServer, localConn)
		if err != nil {
			// 在 copy 的过程中可能会存在网络超时等 error 被 return,只要有一个发生了错误就退出本次工作
			localConn.Close()
			dstServer.Close()
		}
	}()
	// 从 dstServer 读取数据发送到 localUser,这里因为处在翻墙阶段出现网络错误的概率更大
	lsServer.EncodeCopy(localConn, dstServer)
}

以上就是实现一个轻量级 Shadowsocks 的核心代码。其它一些零碎的代码,例如启动入口、配置读写等,可以去 lightsocks 项目中阅读完整代码。

阅读原文

@gwuhaolin gwuhaolin added the 网络 label Nov 3, 2017

gwuhaolin added a commit to gwuhaolin/lightsocks that referenced this issue Nov 5, 2017

@kalasoo

This comment has been minimized.

Show comment
Hide comment
@kalasoo

kalasoo commented Nov 6, 2017

@rickytan

This comment has been minimized.

Show comment
Hide comment
@rickytan

rickytan Nov 29, 2017

穿墙的解释不太对吧,https 也加密的,为什么访问不了 google/twitter

rickytan commented Nov 29, 2017

穿墙的解释不太对吧,https 也加密的,为什么访问不了 google/twitter

@liuminghao233

This comment has been minimized.

Show comment
Hide comment
@liuminghao233

liuminghao233 Dec 3, 2017

实际用对称加密并不能无脑解密 aes加密是分块的
比如说
0x01 0x02 0x03 0x04
加密成 0xd1 0xd2 0xd3 0xd4
如果你缺了先收到前两个 0xd1 0xd2 可以解出0x01 0x02
但后面的0xd3 0xd4 解不出0x03 0x04

请问应该如何解决这个问题 真心不想看源码,大佬求告知一下
原版python写那一大堆实在是看不下去你知道的话给个大概思路我自己CPP实现

liuminghao233 commented Dec 3, 2017

实际用对称加密并不能无脑解密 aes加密是分块的
比如说
0x01 0x02 0x03 0x04
加密成 0xd1 0xd2 0xd3 0xd4
如果你缺了先收到前两个 0xd1 0xd2 可以解出0x01 0x02
但后面的0xd3 0xd4 解不出0x03 0x04

请问应该如何解决这个问题 真心不想看源码,大佬求告知一下
原版python写那一大堆实在是看不下去你知道的话给个大概思路我自己CPP实现

@antonchen

This comment has been minimized.

Show comment
Hide comment
@antonchen

antonchen Dec 6, 2017

为什么都是基于 SOCKS5 来做混淆呢,SOCKS5 会暴露一个端口专门提供服务不便于隐藏,难道是因为 SOCKS5 很快么?

v2ray 有基于 WebSocket 协议的,但是 v2ray 在我使用中非常耗费 CPU,所以放弃了。

提到 v2ray WebSocket 协议的原因是,我可以用 Nginx 反向代理 WebSocket,把翻墙服务隐藏再一个网站中,比起 SOCKS5 协议会减少 Server 端的暴露几率。

不懂代码层面的事情,请指教。

antonchen commented Dec 6, 2017

为什么都是基于 SOCKS5 来做混淆呢,SOCKS5 会暴露一个端口专门提供服务不便于隐藏,难道是因为 SOCKS5 很快么?

v2ray 有基于 WebSocket 协议的,但是 v2ray 在我使用中非常耗费 CPU,所以放弃了。

提到 v2ray WebSocket 协议的原因是,我可以用 Nginx 反向代理 WebSocket,把翻墙服务隐藏再一个网站中,比起 SOCKS5 协议会减少 Server 端的暴露几率。

不懂代码层面的事情,请指教。

@lenovobenben

This comment has been minimized.

Show comment
Hide comment
@lenovobenben

lenovobenben Dec 9, 2017

你这个就是SS最原始的table加密。
SS进化到AEAD不是没有道理的。
你可以看看这篇文章
https://blessing.studio/why-do-shadowsocks-deprecate-ota/

lenovobenben commented Dec 9, 2017

你这个就是SS最原始的table加密。
SS进化到AEAD不是没有道理的。
你可以看看这篇文章
https://blessing.studio/why-do-shadowsocks-deprecate-ota/

@ccsexyz

This comment has been minimized.

Show comment
Hide comment
@ccsexyz

ccsexyz Dec 9, 2017

以其昏昏使人昭昭

ccsexyz commented Dec 9, 2017

以其昏昏使人昭昭

@timqian

This comment has been minimized.

Show comment
Hide comment
@timqian

timqian Dec 9, 2017

@antonchen SOCKS5 的端口不会暴露到公网,在 ss-local 作为 socks server 接收浏览器的请求,在 ss-server 作为 socks client 向目标服务器(比如google)发送请求。
数据加密解密过程发生在 ss-localss-server 之间的通信

timqian commented Dec 9, 2017

@antonchen SOCKS5 的端口不会暴露到公网,在 ss-local 作为 socks server 接收浏览器的请求,在 ss-server 作为 socks client 向目标服务器(比如google)发送请求。
数据加密解密过程发生在 ss-localss-server 之间的通信

@antonchen

This comment has been minimized.

Show comment
Hide comment
@antonchen

antonchen Dec 9, 2017

@timqian 我指的就是 ss-server 的端口

antonchen commented Dec 9, 2017

@timqian 我指的就是 ss-server 的端口

@timqian

This comment has been minimized.

Show comment
Hide comment
@timqian

timqian Dec 9, 2017

@antonchen ss-server 不暴露端口怎么和 ss-local 通信?

timqian commented Dec 9, 2017

@antonchen ss-server 不暴露端口怎么和 ss-local 通信?

@antonchen

This comment has been minimized.

Show comment
Hide comment
@antonchen

antonchen Dec 9, 2017

@timqian 使用 WebSocket 监听在 localhost 上,Nginx 反向代理 WebSocket,使服务隐藏在一个 VirtualHost 中,甚至某个 URL 中。

antonchen commented Dec 9, 2017

@timqian 使用 WebSocket 监听在 localhost 上,Nginx 反向代理 WebSocket,使服务隐藏在一个 VirtualHost 中,甚至某个 URL 中。

@wanghanfeng

This comment has been minimized.

Show comment
Hide comment
@wanghanfeng

wanghanfeng Dec 20, 2017

@rickytan https只是对包体进行加密,防火墙还是能通过包头来解析出你的请求行为,并且阻止。

wanghanfeng commented Dec 20, 2017

@rickytan https只是对包体进行加密,防火墙还是能通过包头来解析出你的请求行为,并且阻止。

@QuantumGhost

This comment has been minimized.

Show comment
Hide comment
@QuantumGhost

QuantumGhost Dec 30, 2017

这种加密(简易替换加密)很容易收到统计学方法的攻击。
比如说,如果你传输的的是 ASCII 英文文档,我们已经知道,英文中最常出现的字母是 e,最常出现的单词是 the,那么攻击者可以统计你发送的报文中出现频率最高的字节和连续三字节,这个很可能就 ethe 对应的密文,攻击者就可以根据对应关系解出密钥了。

QuantumGhost commented Dec 30, 2017

这种加密(简易替换加密)很容易收到统计学方法的攻击。
比如说,如果你传输的的是 ASCII 英文文档,我们已经知道,英文中最常出现的字母是 e,最常出现的单词是 the,那么攻击者可以统计你发送的报文中出现频率最高的字节和连续三字节,这个很可能就 ethe 对应的密文,攻击者就可以根据对应关系解出密钥了。

@lenovobenben

This comment has been minimized.

Show comment
Hide comment
@lenovobenben

lenovobenben Dec 30, 2017

同意 @QuantumGhost ,这样的加密太LOW。
SS/SSR 不知道高到哪里去

lenovobenben commented Dec 30, 2017

同意 @QuantumGhost ,这样的加密太LOW。
SS/SSR 不知道高到哪里去

@argb

This comment has been minimized.

Show comment
Hide comment
@argb

argb Jan 1, 2018

@lenovobenben 其实我觉得这篇文章的意义并不是在于具体的加密算法有多么高级,而是让大家知道了翻墙的原理和一些基本细节。对这方面不太了解的人确实不太懂shadowsocks做了什么,只知道是个代理,有时候也会想为啥不能用nginx走https搞个代理搞定呢?对了,为啥?哈哈。 @wanghanfeng

argb commented Jan 1, 2018

@lenovobenben 其实我觉得这篇文章的意义并不是在于具体的加密算法有多么高级,而是让大家知道了翻墙的原理和一些基本细节。对这方面不太了解的人确实不太懂shadowsocks做了什么,只知道是个代理,有时候也会想为啥不能用nginx走https搞个代理搞定呢?对了,为啥?哈哈。 @wanghanfeng

@lenovobenben

This comment has been minimized.

Show comment
Hide comment
@lenovobenben

lenovobenben Jan 2, 2018

@argb ,原文中这句话,我看了觉得很不合适:
目前采用对称加密算法实现数据混淆的 Shadowsocks 已经能被一些防火墙通过机器学习算法通过特征分析识别出传输的原内容适合合法,而 Lightsocks 的这套混淆算法目前还不能被轻易的识别出来。

这个算法连IV都没有!第一个字节就是 addrType。事实上,我随便写一个脚本都能识别出来,太 easy了。这也叫“不能被轻易识别?”

lenovobenben commented Jan 2, 2018

@argb ,原文中这句话,我看了觉得很不合适:
目前采用对称加密算法实现数据混淆的 Shadowsocks 已经能被一些防火墙通过机器学习算法通过特征分析识别出传输的原内容适合合法,而 Lightsocks 的这套混淆算法目前还不能被轻易的识别出来。

这个算法连IV都没有!第一个字节就是 addrType。事实上,我随便写一个脚本都能识别出来,太 easy了。这也叫“不能被轻易识别?”

@argb

This comment has been minimized.

Show comment
Hide comment
@argb

argb Jan 2, 2018

@lenovobenben 恩 这方面我不是很懂 可以跟作者建议或者讨论下

argb commented Jan 2, 2018

@lenovobenben 恩 这方面我不是很懂 可以跟作者建议或者讨论下

@XiaoFaye

This comment has been minimized.

Show comment
Hide comment
@XiaoFaye

XiaoFaye commented Jan 18, 2018

mark

@i672715631

This comment has been minimized.

Show comment
Hide comment
@i672715631

i672715631 Jan 26, 2018

hmm,持续关注

i672715631 commented Jan 26, 2018

hmm,持续关注

@WANG-lp

This comment has been minimized.

Show comment
Hide comment
@WANG-lp

WANG-lp Feb 10, 2018

这个算法连简单的rc4都不如。另外通过统计学方法可以很快的反推出码表,而且可以很容易playback attack.

另外我举一个简单的例子,这个协议中client直接将socks5进行 “混淆” 后发给server端,攻击者只需要修改握手数据包中第四个byte (ATYP)然后重复发送给服务器,通过判断服务器是否立即关闭链接,如果服务器在3种情况下(对应未加密时的0x01, 0x03, 0x04)没有立即关闭链接,那么这个就可以判定是一个私有协议的socks5代理。 注意这种方式不需要知道码表,只需要判断是否有3种情况没有立即关闭链接即可。 这种方式对于gfw来说简单高效,只需要尝试256种情况。 参见:http://stackissue.com/breakwa11/shadowsocks-rss/shadowsocks-38.html

另外我提到的这些东西在ss的演进中都已经讨论过了,希望大家造轮子的时候先多多提高自己的知识水平。

WANG-lp commented Feb 10, 2018

这个算法连简单的rc4都不如。另外通过统计学方法可以很快的反推出码表,而且可以很容易playback attack.

另外我举一个简单的例子,这个协议中client直接将socks5进行 “混淆” 后发给server端,攻击者只需要修改握手数据包中第四个byte (ATYP)然后重复发送给服务器,通过判断服务器是否立即关闭链接,如果服务器在3种情况下(对应未加密时的0x01, 0x03, 0x04)没有立即关闭链接,那么这个就可以判定是一个私有协议的socks5代理。 注意这种方式不需要知道码表,只需要判断是否有3种情况没有立即关闭链接即可。 这种方式对于gfw来说简单高效,只需要尝试256种情况。 参见:http://stackissue.com/breakwa11/shadowsocks-rss/shadowsocks-38.html

另外我提到的这些东西在ss的演进中都已经讨论过了,希望大家造轮子的时候先多多提高自己的知识水平。

@lenovobenben

This comment has been minimized.

Show comment
Hide comment
@lenovobenben

lenovobenben Feb 21, 2018

@WANG-lp
确实是这样的。这里小白太多,懒得说

lenovobenben commented Feb 21, 2018

@WANG-lp
确实是这样的。这里小白太多,懒得说

@yckyos261

This comment has been minimized.

Show comment
Hide comment
@yckyos261

yckyos261 Feb 24, 2018

难道这篇文章的目的不是为了告诉你怎么去实现一个 ss功能类似的软件吗。。

yckyos261 commented Feb 24, 2018

难道这篇文章的目的不是为了告诉你怎么去实现一个 ss功能类似的软件吗。。

@Doracoin

This comment has been minimized.

Show comment
Hide comment
@Doracoin

Doracoin Mar 2, 2018

文章思路介绍的很清楚,也从楼上的分享中学习了很多,感谢

Doracoin commented Mar 2, 2018

文章思路介绍的很清楚,也从楼上的分享中学习了很多,感谢

@fankeke

This comment has been minimized.

Show comment
Hide comment
@fankeke

fankeke Mar 2, 2018

赞!
作者是想告诉我们ss的原理以及如何实现一个简单的ss,核心点并不在于加密或者可靠性。

fankeke commented Mar 2, 2018

赞!
作者是想告诉我们ss的原理以及如何实现一个简单的ss,核心点并不在于加密或者可靠性。

@waterun

This comment has been minimized.

Show comment
Hide comment
@waterun

waterun Mar 23, 2018

大神能分析一下brook的表现如何?

waterun commented Mar 23, 2018

大神能分析一下brook的表现如何?

@jinlongplus

This comment has been minimized.

Show comment
Hide comment
@jinlongplus

jinlongplus Mar 28, 2018

非常感谢!

jinlongplus commented Mar 28, 2018

非常感谢!

@euphrat1ca

This comment has been minimized.

Show comment
Hide comment
@euphrat1ca

euphrat1ca Apr 4, 2018

good luck!

euphrat1ca commented Apr 4, 2018

good luck!

@CURAS

This comment has been minimized.

Show comment
Hide comment
@CURAS

CURAS Apr 9, 2018

@antonchen 你所说的“v2ray使用的WebSocket协议”是v2ray服务器与其客户端之间的通信协议,客户端与本地需要代理的应用之间仍然是通常使用socks5协议通信的。

CURAS commented Apr 9, 2018

@antonchen 你所说的“v2ray使用的WebSocket协议”是v2ray服务器与其客户端之间的通信协议,客户端与本地需要代理的应用之间仍然是通常使用socks5协议通信的。

@antonchen

This comment has been minimized.

Show comment
Hide comment
@antonchen

antonchen Apr 9, 2018

@CURAS 是这个意思,这样代理可以隐藏在一个网站下。

antonchen commented Apr 9, 2018

@CURAS 是这个意思,这样代理可以隐藏在一个网站下。

@CURAS

This comment has been minimized.

Show comment
Hide comment
@CURAS

CURAS Apr 9, 2018

但其实用这些安全性高计算量大的对称加密算法去实现混淆有点“杀鸡用牛刀”。

对于这种东西来说,谨慎一点总归是好的。这些常用加密算法的性能消耗在当下已经完全可以忽略。而且对于大多数开发者来说,加密算法本身就是很典型的黑箱,只需要会使用就够了,造轮子既没有必要也没有能力。

CURAS commented Apr 9, 2018

但其实用这些安全性高计算量大的对称加密算法去实现混淆有点“杀鸡用牛刀”。

对于这种东西来说,谨慎一点总归是好的。这些常用加密算法的性能消耗在当下已经完全可以忽略。而且对于大多数开发者来说,加密算法本身就是很典型的黑箱,只需要会使用就够了,造轮子既没有必要也没有能力。

@a854363956

This comment has been minimized.

Show comment
Hide comment
@a854363956

a854363956 Apr 19, 2018

@rickytan 穿墙的解释很对,之所以HTTPS加密了,但是还是无法访问的原因是网页访问是通过查询DNS服务器当前域名指向的地址是多少?然后获得地址后在发送TCP协议数据,最后完成一次访问,虽然HTTPS加密了请求内容,但是DNS查询并没有加密内容,而且你用的也是公网的DNS,所以会导致DNS污染,google之所以无法访问,原因是DNS污染,在rfc1928里面表示DNS是在代理服务器解析了,但是火狐浏览器的socket5代理是在本地解析的,没有在代理服务器里面解析,所以火狐的socket5代理是不完整的!
PS: 加密混淆只是为了解决墙识别是否是搭建了代理服务器,如果搭建了直接封你IP,避免墙根据特征来识别流量,比如socket5 的
+---- +---------- +----------+
|VER | NMETHODS | METHODS |
+----+---------- +----------+
| 1 | 1 | 1 to 255 |
+----+-------- --+----------+
就很好识别,然后直接禁止这样的流量通过即可,而ss的目的就是混淆socket5的流量

PS: 当然rfc1928 udp代理的我还是没有弄明白,而且rfc1928里面写的好坑.......

a854363956 commented Apr 19, 2018

@rickytan 穿墙的解释很对,之所以HTTPS加密了,但是还是无法访问的原因是网页访问是通过查询DNS服务器当前域名指向的地址是多少?然后获得地址后在发送TCP协议数据,最后完成一次访问,虽然HTTPS加密了请求内容,但是DNS查询并没有加密内容,而且你用的也是公网的DNS,所以会导致DNS污染,google之所以无法访问,原因是DNS污染,在rfc1928里面表示DNS是在代理服务器解析了,但是火狐浏览器的socket5代理是在本地解析的,没有在代理服务器里面解析,所以火狐的socket5代理是不完整的!
PS: 加密混淆只是为了解决墙识别是否是搭建了代理服务器,如果搭建了直接封你IP,避免墙根据特征来识别流量,比如socket5 的
+---- +---------- +----------+
|VER | NMETHODS | METHODS |
+----+---------- +----------+
| 1 | 1 | 1 to 255 |
+----+-------- --+----------+
就很好识别,然后直接禁止这样的流量通过即可,而ss的目的就是混淆socket5的流量

PS: 当然rfc1928 udp代理的我还是没有弄明白,而且rfc1928里面写的好坑.......

@vidamcmillan

This comment has been minimized.

Show comment
Hide comment
@vidamcmillan

vidamcmillan Apr 24, 2018

@lenovobenben 大神您好,我是小白,我想自己写一个ss客户端,用c#,socket与服务器建立了连接后发送了 0x05 0x01 0x02 这个用户名密码认证的数据后接收不到服务器的返回数据,服务器不是应该挑选一种认证方法返回给我0x05 0x02这样的数据吗?作者原文向服务器认证这里略过了一些,我不太懂,还望您给简单讲接一下,谢谢!

vidamcmillan commented Apr 24, 2018

@lenovobenben 大神您好,我是小白,我想自己写一个ss客户端,用c#,socket与服务器建立了连接后发送了 0x05 0x01 0x02 这个用户名密码认证的数据后接收不到服务器的返回数据,服务器不是应该挑选一种认证方法返回给我0x05 0x02这样的数据吗?作者原文向服务器认证这里略过了一些,我不太懂,还望您给简单讲接一下,谢谢!

@Yrr0r

This comment has been minimized.

Show comment
Hide comment
@Yrr0r

Yrr0r May 3, 2018

这个文章建议顺便弄到wiki上去

Yrr0r commented May 3, 2018

这个文章建议顺便弄到wiki上去

@guange2015

This comment has been minimized.

Show comment
Hide comment
@guange2015

guange2015 May 16, 2018

有个疑问,socks5的解包为什么不放在local做,而要转发到服务器上做。如果sock5实现在本地,remote只处理真正的连接请求,协议完全可以自定义, @WANG-lp 说的问题就不存在。

guange2015 commented May 16, 2018

有个疑问,socks5的解包为什么不放在local做,而要转发到服务器上做。如果sock5实现在本地,remote只处理真正的连接请求,协议完全可以自定义, @WANG-lp 说的问题就不存在。

@jinlongplus

This comment has been minimized.

Show comment
Hide comment
@jinlongplus

jinlongplus May 16, 2018

jinlongplus commented May 16, 2018

@jinlongplus

This comment has been minimized.

Show comment
Hide comment
@jinlongplus

jinlongplus May 16, 2018

jinlongplus commented May 16, 2018

@guange2015

This comment has been minimized.

Show comment
Hide comment
@guange2015

guange2015 May 16, 2018

@jinlongplus 感谢讨论,你说得很好,这份代码从会点code的角度上来说更好,简单易懂,适合打造成自己独有的ss。自己闭源改造,安全性还是可以保证的。

guange2015 commented May 16, 2018

@jinlongplus 感谢讨论,你说得很好,这份代码从会点code的角度上来说更好,简单易懂,适合打造成自己独有的ss。自己闭源改造,安全性还是可以保证的。

@bclicn

This comment has been minimized.

Show comment
Hide comment
@bclicn

bclicn Jun 8, 2018

@rickytan https的HEAD部分是不加密的,也就是HOST能被GFW看到。BODY是加密的,所以传输的内容GFW看不到,你可以试试Chrome或者WireShark看看包内容

bclicn commented Jun 8, 2018

@rickytan https的HEAD部分是不加密的,也就是HOST能被GFW看到。BODY是加密的,所以传输的内容GFW看不到,你可以试试Chrome或者WireShark看看包内容

@InoriHimea

This comment has been minimized.

Show comment
Hide comment
@InoriHimea

InoriHimea Sep 4, 2018

难道不是256个位置,每个位置有255种可能,这样

InoriHimea commented Sep 4, 2018

难道不是256个位置,每个位置有255种可能,这样

@caryxiao

This comment has been minimized.

Show comment
Hide comment
@caryxiao

caryxiao Sep 4, 2018

mark一下, 学习了,

caryxiao commented Sep 4, 2018

mark一下, 学习了,

@dikinova

This comment has been minimized.

Show comment
Hide comment
@dikinova

dikinova Sep 10, 2018

这个项目, proxy部分应该去掉,让内容更简洁.因为,proxy已经有非常成熟的产品了.只要一个隧道即可.golang代码如果能压缩到500行以下,就非常非常理想了.

dikinova commented Sep 10, 2018

这个项目, proxy部分应该去掉,让内容更简洁.因为,proxy已经有非常成熟的产品了.只要一个隧道即可.golang代码如果能压缩到500行以下,就非常非常理想了.

@umbrellary

This comment has been minimized.

Show comment
Hide comment
@umbrellary

umbrellary Sep 26, 2018

这个算法连简单的rc4都不如。另外通过统计学方法可以很快的反推出码表,而且可以很容易playback attack.

另外我举一个简单的例子,这个协议中client直接将socks5进行 “混淆” 后发给server端,攻击者只需要修改握手数据包中第四个byte (ATYP)然后重复发送给服务器,通过判断服务器是否立即关闭链接,如果服务器在3种情况下(对应未加密时的0x01, 0x03, 0x04)没有立即关闭链接,那么这个就可以判定是一个私有协议的socks5代理。 注意这种方式不需要知道码表,只需要判断是否有3种情况没有立即关闭链接即可。 这种方式对于gfw来说简单高效,只需要尝试256种情况。 参见:http://stackissue.com/breakwa11/shadowsocks-rss/shadowsocks-38.html

另外我提到的这些东西在ss的演进中都已经讨论过了,希望大家造轮子的时候先多多提高自己的知识水平。

难道这篇文章的目的不是为了告诉你怎么去实现一个 ss功能类似的软件吗。。赞同这个

umbrellary commented Sep 26, 2018

这个算法连简单的rc4都不如。另外通过统计学方法可以很快的反推出码表,而且可以很容易playback attack.

另外我举一个简单的例子,这个协议中client直接将socks5进行 “混淆” 后发给server端,攻击者只需要修改握手数据包中第四个byte (ATYP)然后重复发送给服务器,通过判断服务器是否立即关闭链接,如果服务器在3种情况下(对应未加密时的0x01, 0x03, 0x04)没有立即关闭链接,那么这个就可以判定是一个私有协议的socks5代理。 注意这种方式不需要知道码表,只需要判断是否有3种情况没有立即关闭链接即可。 这种方式对于gfw来说简单高效,只需要尝试256种情况。 参见:http://stackissue.com/breakwa11/shadowsocks-rss/shadowsocks-38.html

另外我提到的这些东西在ss的演进中都已经讨论过了,希望大家造轮子的时候先多多提高自己的知识水平。

难道这篇文章的目的不是为了告诉你怎么去实现一个 ss功能类似的软件吗。。赞同这个

@TangMonk

This comment has been minimized.

Show comment
Hide comment
@TangMonk

TangMonk Sep 27, 2018

请问如何使用命令行测试,我这样测试服务器不反数据:

$ echo -n '0x01''0x01''0x02' | nc xxxx.com 10490

TangMonk commented Sep 27, 2018

请问如何使用命令行测试,我这样测试服务器不反数据:

$ echo -n '0x01''0x01''0x02' | nc xxxx.com 10490
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment