使用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 協議抓包分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM