{"action":"created","issue":{"url":"https://api.github.com/repos/breakwa11/shadowsocks-rss/issues/38","labels_url":"https://api.github.com/repos/breakwa11/shadowsocks-rss/issues/38/labels{/name}","comments_url":"https://api.github.com/repos/breakwa11/shadowsocks-rss/issues/38/comments","events_url":"https://api.github.com/repos/breakwa11/shadowsocks-rss/issues/38/events","html_url":"https://github.com/breakwa11/shadowsocks-rss/issues/38","id":103760814,"number":38,"title":"ShadowSocks协议的弱点分析和改进","user":{"login":"breakwa11","id":8436963,"avatar_url":"https://avatars.githubusercontent.com/u/8436963?v=3","gravatar_id":"","url":"https://api.github.com/users/breakwa11","html_url":"https://github.com/breakwa11","followers_url":"https://api.github.com/users/breakwa11/followers","following_url":"https://api.github.com/users/breakwa11/following{/other_user}","gists_url":"https://api.github.com/users/breakwa11/gists{/gist_id}","starred_url":"https://api.github.com/users/breakwa11/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/breakwa11/subscriptions","organizations_url":"https://api.github.com/users/breakwa11/orgs","repos_url":"https://api.github.com/users/breakwa11/repos","events_url":"https://api.github.com/users/breakwa11/events{/privacy}","received_events_url":"https://api.github.com/users/breakwa11/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":152,"created_at":"2015-08-28T17:26:08Z","updated_at":"2015-08-31T16:08:35Z","closed_at":null,"body":"本文内容比较偏向于技术，可能有点难看懂 :(\r\n\r\n原ShadowSocks协议的TCP连接部分，使用非常简单的协议，加密前是这样子的：\r\n地址类型(1byte)|地址(不定长)|端口(2byte)\r\n其中，地址类型有三个可能取值：1，3，4，分别对应IPv4，Remote DNS，IPv6\r\n地址的长度就按这个地址类型取值来决定，后面这些不详细介绍了，不是关键的东西。\r\n\r\n而加密的时候，会根据不同的加密算法，在前面添加一些随机字节，再进行加密，以使得即使发送相同的数据，加密的结果也不会相同。但是，同一个加密算法下，前面添加的随机字节的长度是固定的，而最常用的加密算法只有rc4-md5和aes系列（aes-128-cf8和aes-256-cfb等等），而非常的不巧，这些加密算法在使用的时候前面**均添加16字节**，于是前文所说的表示地址类型的那个字节的位置是**完全固定的**（在第17字节上）。\r\n\r\n服务端为了判断数据是否有效，判断的依据就是表示地址类型的那个字节，看它是不是在那三个可能取值，如果不是，立即断开连接，如果是，就尝试解析后面的地址和端口进行连接。\r\n\r\n基本的原理介绍完了，以下介绍如何进行主动探测。\r\n因为SS判断数据的合法性，仅仅依据表示地址类型的那个字节，与此同时，根据信息论，对加密后的数据解密的时候，同一个位置上的数据如果穷举全部256个编码，那么能正好一一对应解密后的全部256个原码，这与密码长度还是用什么加密算法完全无关。那么，可以通过暴力尝试全部256种可能，找出服务器是否有三种情况下没有立即关闭连接，从而成功判断出这个端口开放的是SS服务。成功判断出这是SS服务器为了什么呢？聪明的你应该明白，在这之后你的IP或者你的服务器IP其中一方会落入黑名单。\r\nPS.因为chacha20填充的长度是8字节，和其它加密用的长度并不一样，于是你会发现用chacha20的往往比其它加密算法稳定，只是因为这个长度较少被用于探测。\r\n\r\n关键的问题就是，只需要穷举256种可能，成本太低了，太不安全了。也许clowwindy也意识到这个问题，他在issue中提出到考虑让服务端发现连接的数据不正确的时候，把数据重定向到一个正常的http网站，让其行为看起来比较一致从而不好分辨。但是这个修改还没有完成，为什么clowwindy会被请喝茶？因为如果不阻止，那么加入这个特性后会让ss封锁起来困难很多，所以请喝茶，阻止更新，然后才方便对其实施大规模封杀（毕竟按照新的特性重新写探测代码很麻烦不是）。在我知道clowwindy被请喝茶那天，我已经明白根本不是因为ss不好封杀，而是阻止更新然后去封杀，这个弱点已经被掌握并正在被运用，大规模封杀那天会到来得很快。\r\n\r\n但是，那个改进依然不安全，还是只要穷举256种可能，看一看是不是有253种情况是http/https协议就行了，所以我没有考虑这么做（不过我想得还是粗糙，这点其实是猜的，有误请指出）。如果是多用户服务器，就更简单了，直接端口扫描，看看开放的接口是不是大部分都是http（甚至全都指向同一地方）就行了，总之这种做法特征明显。\r\n\r\n我感觉上，这个如果不从协议上去解决，还是很难避免被封杀，于是在原来的SS协议头，添加了一个包装，在这里复制一下这个包装的结构说明：\r\n标志版本号(1byte)|首包总长度(2byte)|随机填充长度(1byte)|随机填充数据|原ss首数据包|CRC32(4byte)\r\n起关键作用的，就是末尾的CRC32，前面的数据包不管你如何去猜测，有了CRC32作为整个数据包的校验，穷举成本提升到至少256的5次方，即需要尝试10^12次，或1T次，产生的数据量将达到几十T，这样子去暴力破解还不如直接DDOS的成本低。另外，为啥CRC32放最末尾？一是为了方便校验，二是根据加密的特性，前面的数据只要改了一个，就会导致后面的数据完全不一样，这样只要前面的数据一动，后面所有的值就都错了，让CRC32校验充分发挥作用。\r\n另外还有一个关键的地方就是随机填充不定长度的数据。现在其实除了新的Windows C#版客户端，其它平台基本没有把握手包和第一个数据包连起来发送，这样会导致SS客户端发送的第一个数据包的长度，在很多情况下是固定的，从而成为被检测的依据。就算实现了连同第一个数据包一起发送，但某些协议的连接并不会立即产生第一个数据包，而是先等待服务端的回应（比如 SSH），于是依然存在定长的问题（会精确等于16+1+4+2=23字节，只要统计发现你的IP经常产生23字节的TCP首包便可封杀）。为方便其它平台以最少的代码实现，直接在协议头部加入这个随机填充长度，以弥补ss在加密时总是产生定长数据包的问题。\r\n\r\n这个解决方案能极大增加主动探测的难度以及首包长度检测的难度（非首包长度目前没有混淆，尽管还是可以通过非首包长度进行猜测，但毕竟需要的计算资源较大，这样调整后的SS还是能存活较长的时间的），但不想对SS协议做太大的改动从而造成各平台实现的修改困难（在Python或C#上增加支持这个协议才10行代码不到），SS之所以能实现如此多的平台，正是因为其协议简单，容易实现，所以我决定使用比较简单的协议来解决以上问题。\r\n\r\n如果以上协议你还发现存在弱点，请留言告诉我，大家一起讨论——by Breakwa11"},"comment":{"url":"https://api.github.com/repos/breakwa11/shadowsocks-rss/issues/comments/136416769","html_url":"https://github.com/breakwa11/shadowsocks-rss/issues/38#issuecomment-136416769","issue_url":"https://api.github.com/repos/breakwa11/shadowsocks-rss/issues/38","id":136416769,"user":{"login":"breakwa11","id":8436963,"avatar_url":"https://avatars.githubusercontent.com/u/8436963?v=3","gravatar_id":"","url":"https://api.github.com/users/breakwa11","html_url":"https://github.com/breakwa11","followers_url":"https://api.github.com/users/breakwa11/followers","following_url":"https://api.github.com/users/breakwa11/following{/other_user}","gists_url":"https://api.github.com/users/breakwa11/gists{/gist_id}","starred_url":"https://api.github.com/users/breakwa11/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/breakwa11/subscriptions","organizations_url":"https://api.github.com/users/breakwa11/orgs","repos_url":"https://api.github.com/users/breakwa11/repos","events_url":"https://api.github.com/users/breakwa11/events{/privacy}","received_events_url":"https://api.github.com/users/breakwa11/received_events","type":"User","site_admin":false},"created_at":"2015-08-31T16:08:35Z","updated_at":"2015-08-31T16:08:35Z","body":"要检查最近的连接是不是用了相同的IV很烧计算力和内存的"}}