使用底層套接字解碼底層流量,是這次做的重點工作。
首先來捕獲第一個包
# coding:utf-8import socket # 監聽的主機IP host = "192.168.1.100" socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) sniffer.bind((host, 0)) sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) raw_buffer = sniffer.recvfrom(65535) print raw_buffer
下面一行一行解釋上面代碼的意思。
1. 導入socket包
2. 需要監聽的本機ip地址
3. 給socket_protocol變量賦值icmp變量
4. 為sniffer變量創建一個soket對象,該對象為ipv4 原始套接字並指定其協議為icmp
5. 綁定到指定地址和端口進行監聽
6. 為sniffer套接字設置選項參數,使其攜帶ip頭
7. 將監聽端口的套接字收到的原始數據賦值給raw_buffer
8. 打印raw_buffer的值
這個時候,我們使用root權限運行這個腳本,並且開啟另外一個terminal對任意一個地址發送icmp包,我們監聽的接口的recvfrom 會收到回監聽回包到指定地址。recvfrom與recv不同的是 recvfrom會同時接收回包地址。(string, address)的格式
這個時候我們可以看到打印出來的值,是一堆完全看不懂的東西,因為是沒有解碼的狀態,下面我們將對ip頭進行解碼。
使用python的struct和ctypes兩個庫實現這一點。
# coding:utf-8import socket import struct from ctypes import * # 監聽的主機IPhost = "192.168.1.100" # IP頭定義 class IP(Structure): _fields_ = [ ("ihl", c_ubyte, 4), ("version", c_ubyte, 4), ("tos", c_ubyte), ("len", c_ushort), ("id", c_ushort), ("offset", c_ushort), ("ttl", c_ubyte), ("protocol_num", c_ubyte), ("sum", c_ushort), ("src", c_uint), ("dst", c_uint), ] def __new__(self, socket_buffer=None): return self.from_buffer_copy(socket_buffer) def __init__(self, socket_buffer=None): self.protocol_map = {1: "ICMP", 6: "TCP", 17: "UDP"} # readable ip address self.src_address = socket.inet_ntoa(struct.pack("<I", self.src)) self.dst_address = socket.inet_ntoa(struct.pack("<I", self.dst)) # type of protocol try: self.protocol = self.protocol_map[self.protocol_num] except: self.protocol = str(self.protocol_num) socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) sniffer.bind((host, 0)) sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) try: while True: raw_buffer = sniffer.recvfrom(65535)[0] ip_header = IP(raw_buffer[:20]) print "Protocol: %s %s -> %s " % (ip_header.protocol, ip_header.src_address, ip_header.dst_address) except KeyboardInterrupt: pass
1. 導入各模塊
2. 監聽的本機ip地址
3. 使用ctypes 構造一個解析ip頭的結構體(structure) IP
4. 使用from_buffer_copy方法在__new__方法將收到的數據生成一個IP class的實例
5. __init__方法初始化一部分數據保存到對應的實例屬性值中。
6. 特別說明下面代碼, 使用了python struct庫的pack方法 用指定的格式化參數將src 和dst的long型數值轉換為字符串,然后使用socket.inet_ntoa方法將字符串的一串數字轉換為對應的ip格式。最后賦值給對應的src或者dst變量
# readable ip address self.src_address = socket.inet_ntoa(struct.pack("<I", self.src)) self.dst_address = socket.inet_ntoa(struct.pack("<I", self.dst))
7. 一個接收icmp包的服務器,沒什么說的。
8. 無限循環監聽指定端口,將recvfrom收到的數據的第一部分 也就是不要ip地址的部分傳遞給raw_buffer
9. ip頭raw_buffer的前20個字節傳遞給結構體進行解碼。
10. 然后打印。
可以看到大致思路就是,將原型socket數據拿過來,然后通過模擬c語言的結構體,使用python的庫對這個格式的包進行一一對應的解碼,將解碼之后的數據打印出來。
到此為止可以看到,在ip層已經可以解析出數據包從哪兒去哪兒的信息。