zoukankan      html  css  js  c++  java
  • 使用wireshark抓包分析SOCKS5协议


    通信软件课选择了分析SOCKS5协议,想看一下这个协议在网络通信中是如何进行的,遂抓包实践如下。

    编写SOCKS5服务器运行代码(参考自Python编写socks5服务器

    import select
    import socket
    import struct
    from socketserver import StreamRequestHandler, ThreadingTCPServer
    SOCKS_VERSION = 5
    class SocksProxy(StreamRequestHandler):
        def handle(self):
            print('Accepting connection from {}'.format(self.client_address))
            # 协商
            # 从客户端读取并解包两个字节的数据
            header = self.connection.recv(2)
            version, nmethods = struct.unpack("!BB", header)
            # 设置socks5协议,METHODS字段的数目大于0
            assert version == SOCKS_VERSION
            assert nmethods > 0
            # 接受支持的方法
            methods = self.get_available_methods(nmethods)
            # 无需认证
            if 0 not in set(methods):
                self.server.close_request(self.request)
                return
            # 发送协商响应数据包
            self.connection.sendall(struct.pack("!BB", SOCKS_VERSION, 0))
            # 请求
            version, cmd, _, address_type = struct.unpack("!BBBB", self.connection.recv(4))
            assert version == SOCKS_VERSION
            if address_type == 1:  # IPv4
                address = socket.inet_ntoa(self.connection.recv(4))
            elif address_type == 3:  # Domain name
                domain_length = self.connection.recv(1)[0]
                address = self.connection.recv(domain_length)
                #address = socket.gethostbyname(address.decode("UTF-8"))  # 将域名转化为IP,这一行可以去掉
            elif address_type == 4: # IPv6
                addr_ip = self.connection.recv(16)
                address = socket.inet_ntop(socket.AF_INET6, addr_ip)
            else:
                self.server.close_request(self.request)
                return
            port = struct.unpack('!H', self.connection.recv(2))[0]
            # 响应,只支持CONNECT请求
            try:
                if cmd == 1:  # CONNECT
                    remote = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    remote.connect((address, port))
                    bind_address = remote.getsockname()
                    print('Connected to {} {}'.format(address, port))
                else:
                    self.server.close_request(self.request)
                addr = struct.unpack("!I", socket.inet_aton(bind_address[0]))[0]
                port = bind_address[1]
                #reply = struct.pack("!BBBBIH", SOCKS_VERSION, 0, 0, address_type, addr, port)
                # 注意:按照标准协议,返回的应该是对应的address_type,但是实际测试发现,当address_type=3,也就是说是域名类型时,会出现卡死情况,但是将address_type该为1,则不管是IP类型和域名类型都能正常运行
                reply = struct.pack("!BBBBIH", SOCKS_VERSION, 0, 0, 1, addr, port)
            except Exception as err:
                logging.error(err)
                # 响应拒绝连接的错误
                reply = self.generate_failed_reply(address_type, 5)
            self.connection.sendall(reply)
            # 建立连接成功,开始交换数据
            if reply[1] == 0 and cmd == 1:
                self.exchange_loop(self.connection, remote)
            self.server.close_request(self.request)
        def get_available_methods(self, n):
            methods = []
            for i in range(n):
                methods.append(ord(self.connection.recv(1)))
            return methods
        def generate_failed_reply(self, address_type, error_number):
            return struct.pack("!BBBBIH", SOCKS_VERSION, error_number, 0, address_type, 0, 0)
        def exchange_loop(self, client, remote):
            while True:
                # 等待数据
                r, w, e = select.select([client, remote], [], [])
                if client in r:
                    data = client.recv(4096)
                    if remote.send(data) <= 0:
                        break
                if remote in r:
                    data = remote.recv(4096)
                    if client.send(data) <= 0:
                        break
    if __name__ == '__main__':
        # 使用socketserver库的多线程服务器ThreadingTCPServer启动代理
        with ThreadingTCPServer(('127.0.0.1', 9011), SocksProxy) as server:
            server.serve_forever()
    

    本文采用参考链接中curl请求的方法经由代理服务器获取目标网站的信息curl -v --socks5 127.0.0.1:9011 http://www.baidu.com
    对于python编写的client程序

    import socket
    import socks
    import requests
    socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 9011, username=None, password=None)
    socket.socket = socks.socksocket
    head={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'}
    print(requests.get('https://www.baidu.com', head).text)
    

    需要注意的一点是import的socks包是第三方库PySocks,pip安装时使用命令pip install PySocks(参考自No module named 'socks', i have tried everything

    使用SOCKS5服务器脚本和curl命令

    在本地运行SOCKS5服务器脚本,执行curl命令得到如图结果

    放在远端主机上运行SOCKS5服务器脚本,在本地执行curl命令得到如图结果

    这时查询listen的主机端口netstat -anp

    可能需要将Local Address 设置为0.0.0.0,外来的连接才可以连接,查相关的资料(全零网络IP地址0.0.0.0表示意义详谈Linux的netstat查看端口是否开放见解(0.0.0.0与127.0.0.1的区别))果然如此。
    遂将服务器脚本的倒数第二行"127.0.0.1"改为"0.0.0.0",重复之前步骤发现与本地测试的结果差不多,并在该过程中抓取到curl请求远程主机发送百度首页请求的数据包。

    分析抓取到的数据包理解SOCKS5协议的工作过程(感谢socks5代理服务器协议的说明让我预先知道SOCKS5协议数据消息传递的机理)


    握手

    本地发送认证请求,05表示SOCKS5,02表示接受2种认证方法,00表示无需认证的认证方法,01表示GSSAPI

    服务端确认无需认证

    本地查询DNS信息

    第一个01说明TCP,第二个01说明后面是(DNS查询出的)IP地址,3d87b920指目标网站的ip地址61.135.185.32,0050指80端口

    SOCKS5服务器成功连接上目标网站后发送成功响应,ac150007指服务器连接目标网站用到的ip地址172.21.0.7,8a10指使用的端口35344,也可以不说明连接的具体细节,直接0500后面接8个字节的0

    本机向SOCKS5服务器发送目标网站的http请求

    获取到目标网页信息

    挥手
    拓展阅读:SOCKS 5 协议抓包分析

  • 相关阅读:
    表单
    超链接
    图像
    表格
    排列清单控制标
    HTML基本结构
    如何快速查看网页源代码
    TOR的使用
    google搜索新姿势
    [NOIP2017]列队
  • 原文地址:https://www.cnblogs.com/tellw/p/13862546.html
Copyright © 2011-2022 走看看