Skip to content

GoQuiet

Qian Wang edited this page Mar 10, 2018 · 11 revisions

GoQuiet是一个shadowsocks的混淆插件,基本原理为模拟TLS的流量同时将服务器伪装成一个正常的网站服务器

这个混淆思路并非首创:simple-obfs和ShadowsocksR的tls1.2_ticket_auth模式证明这条道是行得通的。此插件对现有的方法进行了改善,其目标为让大规模封锁HTTPS服务器(甚至IP段)成为封锁shadowsocks的唯一有效手段

此插件已在amd64和arm Linux和amd64 Windows上测试过。 其资源使用与shadowsocks-libev相同(即很少),并且几乎没有在原协议上添加传输开销(除了握手阶段)。

下载

下载链接

或者可以通过make clientmake 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认为你在使用的浏览器,这里和你实际使用的浏览器或任何应用都无关。目前支持chromefirefox

实现原理

如上所述,这个的基本原理是插件将shadowsocks的流量伪装为TLS流量。这包括在数据中添加TLS Record Layer以及模拟TLS握手。这两个实现都很简单,但我们可以利用TLS握手包中的数据来实现服务器伪装和抗重放。

TLS的第一个握手包是由客户端发出的ClientHello。其中,我们最关注的是randomsession_ticket这两个field。根据rfc5246random的内容是当前的32位unix时间加上28个随机字节,不过在大多数实现中,所有32个字节都是随机生成的(来源:Wireshark)。 session_ticket则触发了一种称为session resumption的机制,使得服务器可以跳过很多握手包,包括服务器的证书信息。如果没有被CA认证的TLS证书,则必须自签发证书;这对GFW来说会是一种强特征。因此我们需要通过session_ticket避开发送证书,同时也减少握手次数。

客户端使用以下的算法ClientHello获得randomsession_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将会被转发到在配置文件中设置的地址,然后服务器充当客户端和这个地址间的中继。 如果通过,服务器会编写并同时发送ServerHelloChangeCipherSpecFinished,然后客户端同时发送ChangeCipherSpecFinished。 这些消息中的数据都是随机的。 随后服务器充当客户端和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.200www.bing.com),对于GFW代理服务器会变成一个由那个域名所拥有的服务器。