Python之pypcap庫的安裝及簡單抓包工具的實現

該博客首發於www.litreily.tophtml

pypcap是一個對libpcapC庫進行封裝和簡化的面向對象的抓包工具庫,能夠很是方便的用於抓包和過濾,結合dpkt解析庫能夠完成許多網絡數據包的抓取和分析。本文講述的就是如何使用pypcapdpkt庫實現簡單抓包工具,也稱爲嗅探器(sniffer).python

Linux 端安裝 pypcap

sudo apt-get install libpcap-dev
sudo pip install pypcap
複製代碼

這裏有個問題,若是使用Anaconda目錄的pip安裝則可能失敗,目前緣由未明,但官方的python3對應的pip3python2對應的pip均無此問題.linux

Windows 端安裝 pypcap

根據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

這裏提到winpcapwin10間存在兼容性問題,具體什麼問題我也沒搞清楚,以前使用wireshark抓包一直用的winpcap也沒問題。不過我估計和後面要用到的npcap sdk有關吧。既如此,就須要在安裝pypcap前安裝好Npcap,並下載好Npcap SDK編程

下載文件

  1. pypcap 源碼
  2. Npcap
  3. Npcap SDK

安裝

  • 安裝Npcap

安裝下載後的Npcap安裝包,若是電腦帶有無線網卡,記得勾選「support raw 802.11 traffic(and monitor mode) for wireless adapters」。須要注意的是,若是電腦已經安裝過winpcap軟件,在安裝Npcap時會彈窗提示卸載Winpcap,此時須要關閉wireshark或是其它相關的軟件ubuntu

  • 安裝pypcap
  1. Npcap SDK文件夾和pypcap源碼文件夾放在一個目錄下
  2. Npcap SDK文件夾名稱修改成wpdpack
  3. 進入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

findalldevs能夠列出當前操做系統的全部網絡接口,可是windowsLinux的輸出風格不大同樣,下面來看看.

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}']
複製代碼

pcap.pcap

pc = pcap.pcap(devs[3], promisc=True, immediate=True, timeout_ms=50)
複製代碼

以上代碼定義了一個pcap對象,首個參數devs[3]對應接口名,promisc爲真表明打開混雜模式,immediate表明當即模式,啓用將不緩存數據包,timeout_ms表明接收數據包的超時時間

setfilter

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地址及分片信息等.

  • 解析http數據包

anlysisData函數目前只是簡單的檢測及打印解碼後的http包,使用dpkt.ethernet.Ethernet能夠將原始數據包封裝成一個結構化的以太網幀,以後按照網絡協議棧的順序即可逐層解析出鏈路層、網絡層、傳輸層直至應用層.以上代碼先是判斷是否爲IP報文,以後根據端口號判斷是否爲http報文,而後將數據解碼後輸出.

這個例子也很簡單,不少異常狀況也沒考慮,本文主要目的是描述pypcapdpkt的經常使用方法以及抓包工具的實現過程,至於針對具體協議的解析則需繼續學習.

抓包測試

➜ 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

參考

  1. windows 環境下python 安裝 pypcap...
  2. Python黑客編程3網絡數據監聽和過濾
  3. Mac下用python+pypcap+dpkt抓取IP數據包並分析
相關文章
相關標籤/搜索