那麼,就寫一隻Python Sniffer吧!
上次寫Sniffer時,應該是大學二年級的時候了吧那時候, wireshark 還是叫作 Ethereal 呢
當時是為了做專題需要
用 winpcap 提供的範例,自已開網卡等等等
別再回想了,一想到 MFC,我胃就痛了…
用Python寫Sniffer快多了
麻煩的地方大概是
- 使用RAW Socket,需要 root 權限
- WIN OS RAW Socket只能從IP層開始,大概要安裝其它pcap lib才能解決這問題
原始碼
- # -*- encoding: utf-8 -*-
- import binascii
- import socket
- import struct
- import sys
- def arp_parser(packet):
- arp_length = 28 # fixed
- arp = struct.unpack("!2s2s1s1s2s6s4s6s4s", packet[0:arp_length])
- print ("ARP Header :")
- print (" |_ SHA: {0} -> THA: {1}".format(binascii.hexlify(arp[5]), binascii.hexlify(arp[7])))
- print (" |_ SPA: {0} -> TPA: {1}".format(socket.inet_ntoa(arp[6]), socket.inet_ntoa(arp[8])))
- print (" |_ HTYPE : {0}".format(binascii.hexlify(arp[0])))
- print (" |_ PTYPE : {0}".format(binascii.hexlify(arp[1])))
- print (" |_ HLEN : {0}".format(binascii.hexlify(arp[2])))
- print (" |_ PLEN : {0}".format(binascii.hexlify(arp[3])))
- print (" |_ OPER : {0}".format(binascii.hexlify(arp[4])))
- #print (" |_ SHA : {0}".format(binascii.hexlify(arp[5])))
- #print (" |_ SPA : {0}".format(socket.inet_ntoa(arp[6])))
- #print (" |_ THA : {0}".format(binascii.hexlify(arp[7])))
- #print (" |_ TPA : {0}".format(socket.inet_ntoa(arp[8])))
- # Padding dump packet[arp_length:]
- print (" |_ Data : {0}".format(binascii.hexlify(packet[arp_length:])))
- def icmp_parser(packet):
- icmp_length = 4
- icmp = struct.unpack("!BBH", packet[0:icmp_length])
- print ("ICMP Header :")
- print (" |_ Type : {0}".format(icmp[0]))
- print (" |_ Code : {0}".format(icmp[1]))
- print (" |_ Checksum : {0} ({1})".format(icmp[2], hex(icmp[2])))
- # Padding dump packet[icmp_length:]
- print (" |_ Data : {0}".format(binascii.hexlify(packet[icmp_length:])))
- def tcp_parser(packet):
- tcp_length = 20
- tcp = struct.unpack("!HHLLBBHHH", packet[0:tcp_length])
- # Real tcp header length
- tcp_length = (tcp[4] >> 4) * 4
- Flags = ((tcp[4] & 0x0f) << 8) + tcp[5]
- print ("TCP Header :")
- print (" |_ Src Port: {0} -> Dst Port: {1}".format(tcp[0], tcp[1]))
- print (" |_ Sequence : {0} ({1})".format(tcp[2], hex(tcp[2])))
- print (" |_ Acknowledgment : {0} ({1})".format(tcp[3], hex(tcp[3])))
- print (" |_ Length : {0}".format(tcp_length))
- print (" |_ Flags : {0}".format(hex(Flags)))
- print (" |_ Window size : {0}".format(tcp[6]))
- print (" |_ Checksum : {0} ({1})".format(tcp[7], hex(tcp[7])))
- print (" |_ Urgent pointer : {0}".format(tcp[8]))
- # Padding dump packet[icmp_length:]
- print (" |_ Data : {0}".format(binascii.hexlify(packet[tcp_length:])))
- def udp_parser(packet):
- udp_length = 8
- udp = struct.unpack("!HHHH", packet[0:udp_length])
- print ("UDP Header :")
- print (" |_ Src Port: {0} -> Dst Port: {1}".format(udp[0], udp[1]))
- print (" |_ Length : {0}".format(udp[2]))
- print (" |_ Checksum : {0} ({1})".format(udp[3], hex(udp[3])))
- # Padding dump packet[icmp_length:]
- print (" |_ Data : {0}".format(binascii.hexlify(packet[udp_length:])))
- def ip_parser(packet):
- ip_length = 20
- ip = struct.unpack("!BBHHHBBH4s4s", packet[0:ip_length])
- version = ip[0] >> 4
- IHL = (ip[0] & 0xf) * 4
- DiffServ = ip[1] >> 2
- ECN = ip[1] & 0x03
- Flags = ip[4] >> 13
- Frag_offset = ip[4] & 0x1fff
- print ("IP Header :")
- print (" |_ From: {0} -> To: {1}".format(socket.inet_ntoa(ip[8]), socket.inet_ntoa(ip[9])))
- print (" |_ Version : {0}".format(version))
- print (" |_ Header Length : {0}".format(IHL))
- print (" |_ DiffServ : {0}".format(DiffServ))
- print (" |_ ECN : {0}".format(ECN))
- print (" |_ Total Length : {0}".format(ip[2]))
- print (" |_ Identification : {0} ({1})".format(ip[3], hex(ip[3])))
- print (" |_ Flags : {0}".format(Flags))
- print (" |_ Fragment Offset: {0}".format(Frag_offset))
- print (" |_ TTL : {0}".format(ip[5]))
- print (" |_ Protocol : {0}".format(hex(ip[6])))
- print (" |_ Checksum : {0} ({1})".format(ip[7], hex(ip[7])))
- # next header
- if ip[6] == 1:
- # ICMP = 0x01
- icmp_parser(packet[IHL:])
- elif ip[6] == 6:
- # TCP = 0x06
- tcp_parser(packet[IHL:])
- elif ip[6] == 17:
- # UDP = 0x11
- udp_parser(packet[IHL:])
- else:
- pass
- def ethernet_parser(packet):
- eth_length = 14 # fixed
- eth = struct.unpack("!6s6s2s", packet[0:eth_length])
- print ("ETHERNET Header :")
- print (" |_ From: {1} -> To: {0}".format(binascii.hexlify(eth[0]), binascii.hexlify(eth[1])))
- print (" |_ Type: {0}".format(binascii.hexlify(eth[2])))
- # parser next header
- if eth[2] == b'\x08\x06':
- arp_parser(packet[eth_length:])
- elif eth[2] == b'\x08\x00':
- ip_parser(packet[eth_length:])
- else:
- pass
- def linux_main():
- rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
- # Bind the interface, likes eth0
- # rawSocket.bind(("eno50332184", 0))
- while True:
- packet = rawSocket.recvfrom(2048)[0]
- ethernet_parser(packet)
- print ("")
- rawSocket.close()
- def windows_main():
- # create a raw socket and bind it to the public interface
- rawSocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
- # Bind a interface with public IP
- HOST = socket.gethostbyname(socket.gethostname())
- rawSocket.bind((HOST, 0))
- print ("Bind a interface : {0}".format(HOST))
- rawSocket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) # Include IP headers
- rawSocket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) # receive all packages
- while True:
- packet = rawSocket.recvfrom(2048)[0]
- ip_parser(packet)
- print ("")
- rawSocket.close()
- if __name__ == '__main__':
- if sys.platform.lower().startswith("win"):
- windows_main()
- else:
- linux_main()
這隻程式在WinOS, Linus皆可運作
在 Python 2.7, Python 3.5 可正常執行
您好,拜讀了您的大作,獲益良多
回覆刪除紙是想請問,為什麼執行了這個程式,結果只能看到自己送出去的封包呢?
我有重新驗證過,在 Linux 環境是正常的;而在 Windows 環境時,只有 ICMP 是正常的
刪除所以我猜想您應該是在 Windows 環境執行這個程式的
因為 Windows 在 XP SP1 之後,有針對 RAW Socking 有增加一些限制 (防止 DOS)
雖然我沒有測試過,若是不使用 RAW Socking 的話,也許就會正常了
如果你仍然需要 RAW Socking 的話,我還有其它的建議可以試試看
1. 改用 C/C++ 撰寫,使用 Winpcap lib
2. 可以試其它的 Python 套件,如 PyPcap or Scapy,去實現你的需求
https://www.winpcap.org/
https://github.com/pynetwork/pypcap
https://github.com/zlorb/scapy