GoQuiet
GoQuiet是一个shadowsocks的混淆插件,基本原理为模拟TLS的流量同时将服务器伪装成一个正常的网站服务器
这个混淆思路并非首创:simple-obfs和ShadowsocksR的tls1.2_ticket_auth
模式证明这条道是行得通的。此插件对现有的方法进行了改善,其目标为让大规模封锁HTTPS服务器(甚至IP段)成为封锁shadowsocks的唯一有效手段
此插件已在amd64和arm Linux和amd64 Windows上测试过。 其资源使用与shadowsocks-libev相同(即很少),并且几乎没有在原协议上添加传输开销(除了握手阶段)。
或者可以通过make client
或make server
自行编译
使用之前请更改密码,密码可以与shadowsocks的密码相同
服务器端:
ss-server -c <SS配置的路径> --plugin <gq-server可执行文件的路径> --plugin-opts "<gqserver.json配置文件的路径>"
客户端:
ss-local -c <SS配置的路径> --plugin <gq-client可执行文件的路径> --plugin-opts "<gqclient.json配置文件的路径>"
如果使用Windows版shadowsocks客户端,请在插件
里填gq-client.exe的路径,插件参数
填gqclient.json的路径
独立模式应该仅在shadowsocks不支持插件时使用
服务器端:
gq-server -r 127.0.0.1:8388 -c <gqserver.json配置文件的路径>
ss-server -c <SS配置的路径> -s 127.0.0.1 -p 8388
客户端:
gq-client -s <server_ip> -l 1984 -c <gqclient.json配置文件的路径>
ss-local -c <SS配置的路径> -s 127.0.0.1 -p 1984 -l 1080
服务器端:
WebServerAddr
是当流量不来自于shadowsocks时的重定向服务器。 应该设置为客户端配置中ServerName
域名的IP记录。
Key
是密钥。 需与客户端配置中的Key
相同
客户端:
ServerName
是你想让GFW认为你在访问的域名
Key
是密钥
TicketTimeHint
是session ticket过期的时间。使用默认值即可
Browser
是你想让GFW认为你在使用的浏览器,这里和你实际使用的浏览器或任何应用都无关。目前支持chrome
和firefox
如上所述,这个的基本原理是插件将shadowsocks的流量伪装为TLS流量。这包括在数据中添加TLS Record Layer以及模拟TLS握手。这两个实现都很简单,但我们可以利用TLS握手包中的数据来实现服务器伪装和抗重放。
TLS的第一个握手包是由客户端发出的ClientHello
。其中,我们最关注的是random
和session_ticket
这两个field。根据rfc5246,random
的内容是当前的32位unix时间加上28个随机字节,不过在大多数实现中,所有32个字节都是随机生成的(来源:Wireshark)。 session_ticket
则触发了一种称为session resumption的机制,使得服务器可以跳过很多握手包,包括服务器的证书信息。如果没有被CA认证的TLS证书,则必须自签发证书;这对GFW来说会是一种强特征。因此我们需要通过session_ticket
避开发送证书,同时也减少握手次数。
客户端使用以下的算法ClientHello
获得random
和session_ticket
的内容:
# Global variables
# In config file:
preshared_key = '[A key shared out-of-band]'
ticket_time_hint = 3600 # In TLS implementations this is the time in seconds for a session ticket to expire.
# Common values are 300,3600,7200 and 100800
# Calculated on startup:
aes_key = sha256(preshared_key)
opaque = rand32int()
# Random:
iv = randbytes(16)
goal = sha256(str(floor(gettimestamp()/(12*60*60))) + preshared_key)
rest = aes_encrypt(iv,aes_key,goal[0:16])
random = iv + rest
# Session ticket
ticket = randbytes(192,seed=opaque+aes_key+floor(gettimestamp()/ticket_time_hint)))
服务器收到ClientHello
消息后会验证random
字段。 如果它没有通过,整个ClientHello
将会被转发到在配置文件中设置的地址,然后服务器充当客户端和这个地址间的中继。 如果通过,服务器会编写并同时发送ServerHello
,ChangeCipherSpec
,Finished
,然后客户端同时发送ChangeCipherSpec
,Finished
。 这些消息中的数据都是随机的。 随后服务器充当客户端和shadowsocks服务器之间的中继。
gettimestamp()/(12*60*60)
这一句是为了防止重放:
每个ClientHello
中的random
字段都应该是唯一的。 为了检查其唯一性,服务器会缓存random
字段的值。 显然,我们不能永远缓存所有的random
数据,因此我们需要定期清理缓存。 如果我们将缓存过期时间设置为12小时,则在12小时内的重放尝试将失败,但如果防火墙保存ClientHello
并在12小时后重新发送,则该消息将通过服务器上的检查,我们的代理会暴露。 但是,当gettimestamp()/(12*60*60)
是goal
的一部分时,重放将永远不会通过验证。
如果你想在你的代理机器上运行一个正常的网站,你需要有一个域名和证书。域名需要花钱买,或者使用像noip这样的免费DDNS服务。证书则可以从Let's Encrypt免费获得。证书仅仅是给网站服务器(比如Apache和Nginx)使用的,GoQuiet不需要证书
或者,你可以将服务器配置文件中的WebServerAddr
字段设置为外部IP,并将客户机配置文件中的ServerName
字段设置为该IP的域名。由于ClientHello
消息中的Server Name Indication,GFW可以知道所有人尝试访问的域名。如果防火墙通过我们使用的SNI向我们的代理服务器发送了一个ClientHello
消息,那么WebServerAddr
中所写的重定向服务器将会收到此ClientHello
消息。重定向服务器会比对SNI和它的配置文件中所写的域名,如果它们不匹配,则该服务器将拒绝连接并显示错误消息,这可能会暴露我们的代理服务器。如果你将重定向服务器的IP与其域名匹配(例如204.79.197.200
到www.bing.com
),对于GFW代理服务器会变成一个由那个域名所拥有的服务器。