該博客首發於www.litreily.tophtml
pypcap是一個對libpcap
C庫進行封裝和簡化的面向對象的抓包工具庫,能夠很是方便的用於抓包和過濾,結合dpkt
解析庫能夠完成許多網絡數據包的抓取和分析。本文講述的就是如何使用pypcap
及dpkt
庫實現簡單抓包工具,也稱爲嗅探器(sniffer).python
sudo apt-get install libpcap-dev
sudo pip install pypcap
複製代碼
這裏有個問題,若是使用Anaconda
目錄的pip
安裝則可能失敗,目前緣由未明,但官方的python3
對應的pip3
及python2
對應的pip
均無此問題.linux
根據pypcap
官方說明:git
WinPcap has compatibility issues with Windows 10, therefore it's recommended to use Npcap (Nmap's packet sniffing library for Windows, based on the WinPcap/Libpcap libraries, but with improved speed, portability, security, and efficiency). Please enable WinPcap API-compatible mode during the library installation.github
這裏提到winpcap
與win10
間存在兼容性問題,具體什麼問題我也沒搞清楚,以前使用wireshark
抓包一直用的winpcap
也沒問題。不過我估計和後面要用到的npcap sdk
有關吧。既如此,就須要在安裝pypcap
前安裝好Npcap
,並下載好Npcap SDK
。編程
安裝下載後的Npcap安裝包,若是電腦帶有無線網卡,記得勾選「support raw 802.11 traffic(and monitor mode) for wireless adapters」。須要注意的是,若是電腦已經安裝過winpcap
軟件,在安裝Npcap
時會彈窗提示卸載Winpcap
,此時須要關閉wireshark或是其它相關的軟件ubuntu
Npcap SDK
文件夾和pypcap
源碼文件夾放在一個目錄下Npcap SDK
文件夾名稱修改成wpdpack
pypcap
源碼目錄,執行python setup.py install
便可完成安裝在第三步須要注意的是,若是Python版本爲3.7.2(其它大於3.7的版本沒試過)有可能編譯失敗,由於有個頭文件pystate.h
在高版本會有更新,致使結構體_ts PyThreadState
中的某些參數不識別,從而提示錯誤pcap.c(22849): error C2039: 'exc_value': is not a member of '_ts'
等。以後我將版本換至3.6.6後便正常編譯了。windows
安裝完成後,能夠進入python
執行import pcap
查看是否已經能夠正常導入。緩存
import pcap
# list all of the Internet devices
devs = pcap.findalldevs()
print(*devs, sep='\n')
pc = pcap.pcap(devs[3], promisc=True, immediate=True, timeout_ms=50)
# fiter http pcakets
pc.setfilter('tcp port 80')
for ptime, pdata in pc:
print(ptime, pdata)
複製代碼
接下來簡單解釋下幾個主要函數bash
findalldevs
能夠列出當前操做系統的全部網絡接口,可是windows
和Linux
的輸出風格不大同樣,下面來看看.
Linux版輸出簡單明瞭,若我猜的不錯,輸出的首個接口即是電腦的有線接口(本人臺式機,Ubuntu系統),至少在我這是適用的.
➜ python
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pcap
>>> pcap.findalldevs()
['enp2s0', 'any', 'lo', 'nflog', 'nfqueue', 'usbmon1', 'usbmon2']
>>>
複製代碼
在ubuntu
及大部分Linux系統中,都可使用ip route
或是ifconfig
來獲取接口名,據此甚至能夠自動獲取接口名.
Windows版則比較複雜,下面是某臺電腦win10
操做系統輸出的結果,這個直接看是看不出什麼的, 由於使用cmd
指令ipconfig /all
輸出的接口信息並不包含如下內容,而是接口名稱及描述信息等,若是想知道下面接口如何與接口名對應起來,能夠參考後面抓包工具使用註冊表來獲取接口信息,或是打開wireshark
抓包,每一個報文的幀頭都會顯示當前接口的接口信息.
>>> import pcap
>>> pcap.findalldevs()
['\Device\NPF_{839768E4-726A-48BB-9CEC-BD6FD670CB8F}', '\Device\NPF_{C4D1AF17-C5C9-40C5-90F8-17781657FC9E}', '\Device\NPF_{26024876-9711-428F-89D3-B91D2C488AC5}', '\Device\NPF_{E26BFFEF-0644-4C13-8016-EB408AE1D471}', '\Device\NPF_{9ED3674C-211E-4A57-923A-F8DBE6E6B704}', '\Device\NPF_{A0B8B562-F309-44F3-95A1-BF34F5465925}', '\Device\NPF_{9D76B006-6946-4C88-AED2-7F7A9194303C}']
複製代碼
pc = pcap.pcap(devs[3], promisc=True, immediate=True, timeout_ms=50)
複製代碼
以上代碼定義了一個pcap對象,首個參數devs[3]
對應接口名,promisc
爲真表明打開混雜模式,immediate
表明當即模式,啓用將不緩存數據包,timeout_ms
表明接收數據包的超時時間
setfilter
用來設置數據包過濾器,好比只想抓http
的包,那就經過setfilter(tcp port 80)
實現,更加詳細的過濾規則請自行谷歌.
for ptime, pdata in pc:
print(ptime, pdata)
複製代碼
pcap.pcap
對象pc是個動態數據,一般結合for循環或是while循環不斷讀取數據包,數據包會返回時間戳及報文數據.
上面這個小例子就是簡單的說明pcap
經常使用庫函數的使用方法.具體的數據包的存儲及解析須要由解析庫dpkt
來完成.下面是一個更加詳細的抓包工具實例,能夠完成數據包的抓取、解析及存儲.
#!/usr/bin/env python3
# -*- encoding:utf-8 -*-
import pcap
import dpkt
import getopt
import sys
import datetime
import time
import os
import platform
if 'Windows' in platform.platform():
import winreg as wr
IF_REG = r'SYSTEM\CurrentControlSet\Control\Network\{4d36e972-e325-11ce-bfc1-08002be10318}'
def getInterfaceByName(name):
'''Get guid of interface from regedit of windows system Args: name: interface name Returns: An valid guid value or None. Example: getInterfaceByName('eth0') '''
reg = wr.ConnectRegistry(None, wr.HKEY_LOCAL_MACHINE)
reg_key = wr.OpenKey(reg, IF_REG)
for i in range(wr.QueryInfoKey(reg_key)[0]):
subkey_name = wr.EnumKey(reg_key, i)
try:
reg_subkey = wr.OpenKey(reg_key, subkey_name + r'\Connection')
Name = wr.QueryValueEx(reg_subkey, 'Name')[0]
wr.CloseKey(reg_subkey)
if Name == name:
return r'\Device\NPF_' + subkey_name
except FileNotFoundError as e:
pass
return None
def mac_addr(mac):
return '%02x:%02x:%02x:%02x:%02x:%02x'%tuple(mac)
def ip_addr(ip):
return '%d.%d.%d.%d'%tuple(ip)
def captureData(iface):
pkt = pcap.pcap(iface, promisc=True, immediate=True, timeout_ms=50)
# filter method
filters = {
'DNS': 'udp port 53',
'HTTP': 'tcp port 80'
}
# pkt.setfilter(filters['HTTP'])
pcap_filepath = 'pkts/pkts_{}.pcap'.format(time.strftime("%Y%m%d-%H%M%S",
time.localtime()))
pcap_file = open(pcap_filepath, 'wb')
writer = dpkt.pcap.Writer(pcap_file)
print('Start capture...')
try:
pkts_count = 0
for ptime, pdata in pkt:
writer.writepkt(pdata, ptime)
# anlysisData(pdata)
printRawPkt(ptime, pdata)
pkts_count += 1
except KeyboardInterrupt as e:
writer.close()
pcap_file.close()
if not pkts_count:
os.remove(pcap_filepath)
print('%d packets received'%(pkts_count))
def printRawPkt(time, data):
eth = dpkt.ethernet.Ethernet(data)
print('Timestamp: ', str(datetime.datetime.utcfromtimestamp(time)))
print('Ethernet Frame: ', mac_addr(eth.src), mac_addr(eth.dst))
if not isinstance(eth.data, dpkt.ip.IP):
print('')
return
ip = eth.data
# get fragments info
do_not_fragment = bool(ip.off & dpkt.ip.IP_DF)
more_fragments = bool(ip.off & dpkt.ip.IP_MF)
fragment_offset = ip.off & dpkt.ip.IP_OFFMASK
print('IP: %s -> %s (len=%d ttl=%d DF=%d MF=%d offset=%d)\n' % (
ip_addr(ip.src), ip_addr(ip.dst), ip.len, ip.ttl,
do_not_fragment, more_fragments, fragment_offset))
def anlysisData(data):
packet = dpkt.ethernet.Ethernet(data)
if isinstance(packet.data, dpkt.ip.IP):
ip = ip_addr(packet.data.dst)
if packet.data.data.dport == 80 or packet.data.data.sport == 80:
try:
print(packet.data.data.data.decode('utf-8', errors='ignore'))
except UnicodeDecodeError as uderr:
print(uderr.__str__())
def main():
if 'Windows' in platform.platform():
iface = getInterfaceByName('Router')
else:
iface = 'enp2s0'
captureData(iface)
if __name__ == "__main__":
main()
複製代碼
getInterfaceByName
根據接口名稱,經過查找註冊表信息獲取pcap
所需的接口設備信息,適用於Windows系統.至於Linux系統,直接經過ifconfig
獲取便可,至於自動獲取功能,目前還沒寫,之後再說吧.
爲了將數據包存儲到.pcap
文件(此類文件可使用wireshark打開)中,能夠經過dpkt.pcap.Writer
對象使用writepkt
函數不斷寫入文件.
pcap_file = open(pcap_filepath, 'wb')
writer = dpkt.pcap.Writer(pcap_file)
for ptime, pdata in pkt:
writer.writepkt(pdata, ptime)
複製代碼
printRawPkt
是個很是簡單的打印數據包基本信息的函數,最多僅打印至ip
信息,打印格式以下:
Timestamp: 2018-12-31 13:58:39.850904
Ethernet Frame: 00:e0:4c:5a:0a:78 00:0f:e9:61:30:00
IP: 192.168.1.76 -> 59.111.160.197 (len=52 ttl=64 DF=1 MF=0 offset=0)
複製代碼
信息包含時間戳,以太網幀的MAC
地址,IP
地址及分片信息等.
anlysisData
函數目前只是簡單的檢測及打印解碼後的http
包,使用dpkt.ethernet.Ethernet
能夠將原始數據包封裝成一個結構化的以太網幀,以後按照網絡協議棧的順序即可逐層解析出鏈路層、網絡層、傳輸層直至應用層.以上代碼先是判斷是否爲IP
報文,以後根據端口號判斷是否爲http報文,而後將數據解碼後輸出.
這個例子也很簡單,不少異常狀況也沒考慮,本文主要目的是描述pypcap
和dpkt
的經常使用方法以及抓包工具的實現過程,至於針對具體協議的解析則需繼續學習.
➜ mkdir pkts
➜ sudo ./pktcap.py
Start capture...
Timestamp: 2018-12-31 13:58:37.148964
Ethernet Frame: 00:36:76:6c:28:fe 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:37.148978
Ethernet Frame: 00:36:76:6c:28:fe 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:37.529024
Ethernet Frame: 00:36:76:6c:28:fe 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:37.809011
Ethernet Frame: 98:e0:d9:a4:50:1d 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:39.850904
Ethernet Frame: 00:e0:4c:5a:0a:78 00:0f:e9:61:30:00
IP: 192.168.1.76 -> 59.111.160.197 (len=52 ttl=64 DF=1 MF=0 offset=0)
Timestamp: 2018-12-31 13:58:39.862890
Ethernet Frame: 00:0f:e9:61:30:00 00:e0:4c:5a:0a:78
IP: 59.111.160.197 -> 192.168.1.76 (len=40 ttl=55 DF=1 MF=0 offset=0)
Timestamp: 2018-12-31 13:58:40.289465
Ethernet Frame: b0:19:c6:17:0a:57 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:40.369068
Ethernet Frame: a4:d1:8c:0b:54:12 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:41.859034
Ethernet Frame: a0:4e:a7:e0:65:3d 33:33:00:00:00:16
Timestamp: 2018-12-31 13:58:42.079218
Ethernet Frame: 8c:6d:50:7d:f9:fc ff:ff:ff:ff:ff:ff
IP: 0.0.0.0 -> 255.255.255.255 (len=352 ttl=64 DF=0 MF=0 offset=0)
^C10 packets received
➜ cd pkts
➜ ls
pkts_20181230-185017.pcap pkts_20181231-203416.pcap pkts_20181231-215837.pcap
複製代碼
代碼已上傳至github Python-demos sniffer.py