通信軟件課選擇了分析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 協議抓包分析