Shadowsocks是如何被检测和封锁的


作者: Alice, Bob, Carol, Jan Beznazwy, Amir Houmansadr

演讲者: David Fifield

这场演讲介绍了我们的IMC'20论文: Shadowsocks是如何被检测和封锁的

最后修改日期: 2020年10月27日,星期二

这场演讲介绍了我们的IMC'20论文: Shadowsocks是如何被检测和封锁的

你可以点击视频下方的cc按钮来选择中文或英文字幕。

这是关于"中国是如何检测和封锁Shadowsocks的"的论文介绍, 该论文出自GFW Report,Jan Beznazwy和Amir Houmansadr。 我是David Fifield。我今天代替作者做论文介绍, 因为他们中大多数都是匿名的。 我有在这个领域的研究经验, 而且在作者的帮助下, 我已经完全熟悉了他们的这项工作。

对这项工作总结成一句话就是: 中国的防火长城已经采用 被动流量分析与主动探测相结合的手段 来检测和封锁Shadowsocks。

现在让我们来讲一下这些词是什么意思。 Shadowsocks是一个加密代理协议, 其设计旨在让协议难以被审查者识别。 这个审查绕过(翻墙)协议在中国非常流行。 而且作为信息管控任务的一部分, 防火长城努力找出并封锁各种类型的代理服务器, 其中包括Shadowsocks。 而事实上,从2019年5月起, 已经有许多来自中国的用户汇报 他们的Shadowsocks服务器被墙。 这些封锁有时发生在政治敏感时期。 但当时对封锁原理都缺乏解释。 这项研究揭示了封锁原理。

首先,在Shadowsocks中, 客户端与服务端之间的连接是加密的。 而且这种加密后, 观察者看到的全部都是密文。 这一点与TLS不同。 TLS的报头中含有明文。 而Shadowsocks流量则全部是密文。 如果你观察就会发现Shadowsocks的流量 就好像一列列均匀随机的字节。 而其就是这么设计的。 这样的设计意味着,你不能仅仅用一个 简单的正则表达来匹配出所有的Shadowsocks流量。 你得更努力一些才行。

现在如果你想流量这样的随机,这样的缺乏特征, 是不是本身就是一种特征呢? 那你想的绝对正确。 事实上,这项研究揭示了防火长城 使用在TCP流中数据包的长度和熵作为 其识别Shadowsocks流量的第一步。

现在我来解释下刚刚提到的 主动探测是啥意思。 这项研究揭示了: 防火长城识别Shadowsocks流量的过程分两步: 第一步被动,第二步主动。 第一步,防火长城流量分析出 其怀疑是Shadowsocks的连接。 而在第二步中, 防火长城会假装成Shadowsocks客户端, 从自己的IP地址主动去连接它怀疑的服务器。 然后观察被探测的服务器会怎样回应。 你可以把第一步的目的想成“怀疑”, 而第二步的目的则为“确认”。

现在你就能理解了: 主动探测是为了在识别中增加准确率,降低成本。 如果你仅仅用流量分析被动地识别Shadowsocks流量, 那么假阳性可能会高得不可接受。 而另一方面,如果你对每一个经过防火长城的连接, 都进行主动探测的话, 那你又会因为需要发送的 主动探测太多而管不过来。 因此你可以把步骤一想成是对步骤二的预先过滤。

这当然不是第一个记录中国采用 主动探测来识别翻墙协议的研究。 早在2011年,就有研究显示中国以主动探测 的方式来识别Tor和其他VPN协议。 但防火长城用来识别Shadowsocks的技术 可谓又达到了新的高度。

我们又是怎么知道这些的呢? 你可能已经猜到了文章作者的调查方法。 她们在中国境外搭建了自己的Shadowsocks服务器; 然后又从中国用自己的客户端去 穿过防火长城,连接到搭建好的服务器。 然后再观察还有什么连接 到了她们的服务器上。 她们还搭建了对照服务器, 她们自己的客户端从未连接到上面, 对照服务器只是用来区分主动探测 和一般的互联网扫描。 她们的实验大概持续了4个月。

目前已经有许多许多的Shadowsocks实现了。 而在这项实验中, 作者选用了其中最受欢迎的两个实现: 分别叫做Shadowsocks-libev和Outline。 它们是对同一个协议的两个相互独立的实现。

在为期4个月的实验中得到的一个主要观察是 防火长城会使用各种各不同类型的主动探测: 一部分主动探测是对合法连接的重放, 另一部分则不是。 合法连接可能被储存起来, 然后经过长得惊人的时间才被重放。 另一部分非重放探测的数据包, 有着奇特的长度分布。 主动探测看上去来自上千个不同的源IP地址。

现在我们来谈谈基于重放的主动探测。 首先这些重放基于合法客户端发出的连接。 具体来讲,它们是基于 合法客户端发出的第一个数据包。 有的重放和原连接一模一样, 而有的重放则是在特定位置上 改变了1个,2个或者更多的字节。

那么审查者发送这些重放探测的目的是什么呢? 原来,审查者是想利用存在于Shadowsocks协议中的某些弱点。 你看,协议并没有规定当一个服务器遇到 重放攻击时应该如何响应。 如果一个实现对重放攻击不做任何的过滤, 那么它就会向代理之前合法客户端发出 的请求一样,去代理重放攻击的请求, 进而将密文的代理流量发送回主动探测者。 虽然审查者因为不知道密码 而无法解密代理流量, 但是服务器向审查者返回了一串密文 这件事本身就足以暴露其为Shadowsocks。

即使是使用了重放过滤的Shadowsocks实现, 仍可能在其服务器关闭连接时的 一些边界情况上暴露信息。 审查者会改变原始连接中一些特定位置上的 字节后再重放,可能是为了绕过重放过滤器。

因为很容易找到重放攻击所基于的原始连接, 所以我们可以测量从合法连接被发送到 其重放攻击被发送的延迟。 请看这幅累积分布函数(CDF)图。 因为同一个合法连接可能被重放多次, 因此暗色线仅表示合法连接第一次被重放的延迟; 而浅色线则表示所有重放的延迟。 如你所见,25%的第一次重放发生在1秒内, 相当于是紧随合法连接之后。 但这幅图还有一个长的惊人的尾巴: 一些重放的延迟是以分钟,小时,甚至是天来计数的。

现在我们来看非重放的主动探测, 这些探测的数据看上去完全随机, 但又并非基于之前的任何合法连接。 你可能发现了这个包长度分布很奇怪。 你看这些长度小于50字节的包, 它们如“三重奏”一般地分布在 8, 12, 16, 22, 33, 41,和49字节。 比如说基于8字节“三重奏”, 包括了数量大致相同的长度为7,8,9字节的探测包。 除此之外,请注意不同类型的非重放探测的比例: 绝大多数的非重放探测的 长度为整整221字节。 这是一个有趣而又发人思考的包长度分布。 论文作者觉得她们至少可以部分地解释

为什么审查者会发送这些长度的探测数据包。 你看,当你向Shadowsock服务器 发送未经认证的随机数据时, 不同的数据长度会引起服务器不同的反应。 如果你发送了过少的数据, 服务器会期待你发送更多的数据, 因此服务器最终会超时。 但当你发送的数据长度超过这一阀值时, 服务器会尝试验证它受到的数据, 而之后又因验证失败而关闭连接。 我不会介绍过多的细节, 但你要知道Shadowsocks 可以配置使用不同的加密方式 而不同加密方式的初始向量长度会不同。

你可以看到,很多的“三重奏”, 长度都是骑跨在服务器不同反应的临界值上的。 服务器的反应包括超时和发送RST关闭连接等。 如第一行所示,如果你向服务器发送了 7或者8字节的数据后,服务器将超时。 但如果你发送了9字节, 那么服务器就会立即发送RST来关闭连接。 因此这个服务器反应可以被识别。 但这个分析并不能完全地 解释“三重奏”的分布。 因为,比如说,长度为32,33,34, 或者长度为40,41,42字节的“三重奏”, 就不位于任何临界值附近。 另外221字节的探测长度也不在临界值上。

现在来讲主动探测的源头。 在为期4个月的实验中, 作者的Shadowsocks服务器收到了 超过50,000次主动探测, 它们来自超过12,000个不同的IP地址。 所有这些IP地址都是中国的。 由上述观察可知,仅仅简单的枚举并屏蔽 所有主动探测的IP地址并不容易。 但这也不是什么令人惊讶的结论, 因为之前的研究工作就早已发现了 审查者会用大量不同的 IP地址来主动探测。 现在我们把这12,000多个在这次研究中 发现的IP地址与之前工作中发现的做对比。 它们之前的重合并不多, 但这也不是很令人惊奇的, 因为之前的研究早已发现 这些主动探测所用的IP地址随时间会发生大量的变化。

虽然这些主动探测看似来自 成千上万的不同IP地址, 但它们很可能是被一小撮进程集中控制的。 证据来自TCP层的旁道信息泄露: TCP时间戳。 TCP时间戳是一个32位的计数器, 其以固定的速率随时间增长。 其被附在每一个(非RST的)TCP包上面。 不同的计算机通常不会有 相同的TCP时间戳序列。 因为这个序列会在系统重启时, 被归零或初始化为一个随机值 这张图显示了来自上千个不同IP地址的 数据包的TCP时间戳随时间的变化, 你可以看到,虽然它们来自不同的IP地址, 但它们都可以被归为很少的几个TCP时间序列。 这些序列的增长速率为250 HZ或1,000 HZ。 那条1,000HZ的线由20个 相距很近的数据点拟合而成。 但可以确定它们的变化率 更接近1000HZ而非250HZ。 TCP时间戳的观察与之前研究中的发现一致。

其他许多不同网络层的指纹 也与之前研究的发现很像。 但TCP源端口号则是一个例外。 先前的工作发现主动探测的TCP源端口号 近乎是均匀随机分布(于1-65535)的。 但在这项研究中, TCP源端口号有很大一部分与 Linux的临时端口相吻合。

现在可以确定防火长城会主动探测 Shadowsocks服务器。 但防火长城是如何怀疑上 Shadowsocks服务器的呢? 以下两个事实辅助了作者的调查:

  1. 重放攻击通常紧随合法连接之后;
  2. 重放攻击仅仅基于第一个携带数据的包。

作者因此设计实验, 在建立TCP连接后仅发送一个数据包。 发送的数据包的长度和熵由作者设置。

虽然我们没有在图中看到尖锐的拐点, 但高熵的数据包比低熵的 数据包更有可能被重放。

作者还确定防火长城也会根据 包的长度来怀疑Shadowsocks流量。 请看这幅累积分布函数(CDF)图。 灰线代表作者自己的连接。 她们发送了长度从1到1,000字节 均匀随机分布的数据包。 现在你看,非重放的探测包 的长度集中在221字节。 而绝大部分重放探测包 的长度则在160到700字节之间。 这个区间长度之外的连接极少被重放。 而即使是在这个区间之内, 一些长度的连接也更有可能被重放。 你会发现代表重放探测的这条线呈阶梯状。 事实上,详细结构如下: 对于长度为160到384字节的包, 如果其长度对16取模后为9, 则其更有可能被重放。 对于长度为246到700字节的包, 如果其长度对16取模后为2, 则其更有可能被重放。 这长度在两个区间的重叠区域的包, 其长度对16取模后的结果则以2和9为主。 作者对这一现象没有好的解释, 她们只是发现了这个让人着迷的包长度分布。

那么我们又能怎么缓解 对Shadowsocks的主动探测呢? 我们已经知道了探测分两步, 那么阻挡其中任何一步就足以挫败探测。 你既可以选择绕过被动地流量分析, 也可以选择绕过主动探测模块。 绕过流量分析就是说去改变会被 防火长城用来分析的特征: 也就是包的熵和长度。 改变包的熵并不容易, 因为这需要根本性地改变协议本身。 而你有更多地余地来改变包的长度。 比如说新版本的Outline会 合并两个连续的包: 这样一来其包的长度分布就可能 不同于防火长城期待的流量特征。

另一个有意思的发现是基于一个叫 Brdgrd(Bridge Guard)的工具. 这是一个你可以安装在 Shadowsocks服务器上的工具。 它可以让客户端发送的每个包的长度变小, 这是通过在每次连接的初始阶段, 改变服务器TCP窗口大小来实现的。 Brdgrd配合Shadowsocks使用 有着种种的缺点和注意事项, 但可以看到在这项实验中, 当Brdgrd被开启时, 主动探测被有效地缓解了, 尽管不是完完全全地阻止了。 你还可以改变服务器对探测的 回应方式,从而避免被识别。

刚刚我给你展示过这个表格, 但我其实撒了一点点谎: 这个表格展示的其实是旧版本的Shadowsocks。 部分因为这项研究的成果, 一些新的Shadowsocks版本, 试图消除超时和关闭连接这样的不一致反应。 新版本的Shadowsocks的反应 更像是这幅图所示的那样。 我不会介绍太多关于AEAD 在新的Shadowsocks协议中的细节, 但你可以看到当在新版Shadowsocks 中使用了推荐的AEAD后, 由于缺乏验证,在使用流加密协议时, 服务器的反应无法做到完全一致地超时。 但开发者已经尽力保持反应一致, 来消除其特征。

总而言之,中国的防火长城已经开始 使用被动的流量分析和主动探测来识别Sadowsocks 主动探测可被连接中的第一个带有数据的包触发。 而且拥有特定长度的包, 和高熵的包更容易触发重放。 与许多种不同的主动探测: 一些是重放攻击,另一些则不是。 主动探测来自许多不同的IP地址, 但有被集中控制的迹象。 可以通过阻断防火长城对Shadowsocks 识别过程中两步的任一一步, 来阻止Shadowsocks被识别。

感谢您的收看。如果您有任何的疑问或评论, 尽情直接与作者们联系。 研究中的源代码和数据在下方链接中。


评论区