2016年9月25日星期日

Shadowsocks 的定制和 “不安全性” 的分析

用 Shadowsocks 有很长一段时间了,确实非常好用,速度不错而且能够满足很多工作中的需求。之前也只是一直用着没有做过多的分析,直到最近看见 ShadowsocksR 这个非官方的项目发现 Shadowsocks 稍做修改便能够实现定制化,比较方便。

SS 的工作流程图下图所示:


流程图中其实显示的很清楚了,要定制自己的 Shadowsocks 服务只需要对 # 2 处的代码进行相应的混淆,而在 # 3 处进行反混淆即可,通过看 SS 代码可以看到这部分的内容在 shadowsocks/tcprelay.py 文件定义的类 TCPRelayHandler 方法 _handle_stage_stream() 中处理,该段的代码如下所示:

def _handle_stage_stream(self, data):
    if self._is_local:
        # ss-local encrypt data and send to ss-server, _remote_sock -> ss-server
        if self._ota_enable_session:
            data = self._ota_chunk_data_gen(data)
        data = self._encryptor.encrypt(data)
        self._write_to_sock(data, self._remote_sock)
        ...

可以看到 ss-local 加密之后写进 socket 发往 ss-remote,这种情况下的 data 为明文 data,此时加密方式可以自己定义,也可以自己修改报文,伪造其他协议。使用 SSR 来免流就是用的这个方法,伪造成 HOST 为服务商的 HTTP 请求达到欺骗基站的目的。

这种情况下还需要在 ss-server 做对应的解密,找到该类的 _on_remote_read() 函数进行相应的反操作即可。

def _on_remote_read(self):
    # handle all remote read events, deal with HTTP request data.
    data = None
    ...
    if self._is_local:
        data = self._encryptor.decrypt(data)
    else:
        data = self._encryptor.encrypt(data)

另外说一点,SS 现在确实在部分情况下可以被较准确地探测,前提是:

1. 使用了 RC4 、AES-256-* 类型的流加密方式,加密之后前 16 字节为随机生成的 IV,第 17 字节表示通过 SS 的协议类型(1 - IPv4,3 - DNS,4 - IPv6),紧跟着从 18 字节开始表示目的地址,长度不定。
2. 没有开启 SS 的 AutoBan 功能,防火墙没有做其他类型的尝试数量限制。

SS Server 端的特性是,如果解密之后的明文在第 17 字节位置上的值不是(1、3、4)中的一个,那么就立马断开连接,否则就继续运行对后面的目的地址进行连接和请求。

而 breakwa11 给出的测探 SS Server 的方法并不是要破解整个密文,而是构造特定密文给 SS Server 去解密,并观察响应。前面 16 字节随便构造,这段密文总可以解密成功,虽然这里的明文完全没有办法知道是啥,不过也不需要知道就是了。根据流加密的特性,遍历 17 字节位置上的所有情况,总有解密之后为(1、3、4)中的某一个的,这时的 SS Server 不会立马断开,而是去解密 18 字节位置开始的目的地址,这样通过 SS Server 的响应差别便可以完成测探。

第一次测探确认成功之后会进行第二次测探,修改 18 字节位置(即:目的地址),虽然这里没办法之后解密之后的目的地址是什么,但是目前 IPv4 基本上全被占用的情况下(假设),即使修改目的地址仍然可以建立连接,如果第二次也不是马上断开,那么就能够确定是运行着 SS Server。

这个探测方法确实是可以成功探测到特殊情况下 SS Server 的存在,不过感觉意义不大,有点像鸡蛋里头挑骨头的做法。而且这也不是 SS 的错,只能说是这类型加密算法的特性,SS 本来就是一个加密协议这样做也没什么有问题的地方。

同时可以发现,breakwa11 在这个 comment 里也有提到,chacha20 这类使用 8 字节 IV 的加密更少被探测,因为默认的 AES-256-* 是 16 字节 IV,而 GFW 主要探测的为 16 字节 IV,所以换成 chacha20 往往 SS 速度会得到提升,因为绕过了部分区域的 QoS。(但并不是这种不能被探测,根据上面的做法修改第 10 位开始的目的地址就行了)

*附上抓的部分请求,可以大致描绘 SS 的网络活动,比较简单不具体分析了。