1. 程式人生 > >pcap檔案的python解析例項

pcap檔案的python解析例項

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

                最近一直在分析資料包。
同時也一直想學python。

湊一塊兒了...於是,便開工了。座椅爆炸!

正文

首先要說的是,我知道python有很多解析pcap檔案的庫,這裡不使用它們的原因是為了理解pcap檔案的格式細節。使用tcpdump你可以很容易抓取到一系列的資料包,然而tcpdump並沒有分析資料包的功能,如果想從這個抓包檔案中分析出一些端倪,比如重傳情況,你必須使用wireshark之類的軟體,用wireshark開啟tcpdump抓取的pcap檔案,如果你看到了一堆堆的深紅色(類似靜脈血管裡流出的豬血的顏色)的資料包,那麼這些包一定是“在協議層看來”異常的資料包,包括但不限於重傳,亂序等等,欲知詳情,請在wireshark的過濾器中敲進去“tcp.analysis.”然後就會自動補全,這一切簡直方便到極點。如果你還想看一些全域性的統計資料,那麼請點選”統計“選單的第一個”捕獲檔案屬性“,你會看到更多的資訊。雖然資料包早就已經過去,但是雁過留聲,我們通過抓取的資料包,還是可以得到更多的資訊,多謝有wireshark/tshark(一個字元介面的pcap檔案分析工具,類似wireshark,但更適合玩機械鍵盤的命令列粉們使用)/shookshark(...)這些工具,使得我們真實能夠分析pcap檔案以獲取資訊。
        然而,這些我覺得還不夠。
        有一個簡單的需求,我想得到在一個TCP連線中,一個端節點一共傳送了多少位元組的TCP載荷資料,包括正常傳送以及重傳。我沒有在wireshark中找到得到這個資料的功能,於是我迫不及待自己寫一個。廚師還怕沒肉吃嗎?
        但有個前提,那就是我必須搞明白pcap檔案的格式,因為我想裸分析pcap檔案,試圖找出每一個感興趣資料包的TCP載荷(不包括TCP頭和IP頭)長度,然後將其累加。這樣我必須知道pcap檔案的格式細節才行。
        幸運的是,pcap檔案非常簡單,就像我幾乎10年前分析Windows PE檔案一樣,如今依然循著老路做著同樣的事情。
        如果你不善於查文件,那麼作為一個程式設計者,看libpcap的原始碼也是個不錯的選擇,幾乎和任何檔案格式一樣,pcap也是一個自描述的格式(這個自描述設計的不夠優雅,以至於後來出現了pcapng檔案格式,後面我會寫一篇文章單獨論述之),整體包括檔案頭和資料載荷,這裡所謂的資料載荷就是網路資料包。在libpcap的pcap.h檔案中,結構體pcap_file_header描述了檔案頭:
struct pcap_file_header {    bpf_u_int32 magic;    u_short version_major;    u_short version_minor;    bpf_int32 thiszone;    /* gmt to local correction */    bpf_u_int32 sigfigs;    /* accuracy of timestamps */    bpf_u_int32 snaplen;    /* max length saved portion of each pkt */    bpf_u_int32 linktype;    /* data link type (LINKTYPE_*) */};
具體我就不解釋了,待會兒我會用一個例項來解析。緊接著這個檔案頭,後面就是一個個資料包了,為了描述每一個數據包的元資訊,每一個數據包都會有一個描述頭:
struct pcap_pkthdr {    struct timeval ts;    /* time stamp */    bpf_u_int32 caplen;    /* length of portion present 由於tcpdump可以設定-s引數指定抓取的長度,這個欄位表示實際抓取的資料包長度 */    bpf_u_int32 len;    /* length this packet (off wire) 這個欄位表示資料包的自然長度 */};

這個結構體描述了資料包抓取的時間資訊以及長度資訊,在這個結構之後才會是資料包,因此一個典型的pcap檔案應該是如下所示:




這簡直清晰至極啊,再次看我的那個需求,我想統計的兩個量怎麼得到呢?

一個TCP連線實際傳送的位元組數:每一個數據包的TCP載荷長度的加和。
一個TCP理論上應該傳送的位元組數:結束的TCP序列號與初始序列號之差。
有了上面的論述,我覺得這個需求超級簡單就實現了,為了展示一下學習python的出血效果,給出以下的程式碼:
 #!/usr/bin/python import sys import
socket import struct filename = sys.argv[0] filename = sys.argv[1] ipaddr = sys.argv[2] direction = sys.argv[3] packed = socket.inet_aton(ipaddr) ip32 = struct.unpack("!L", packed)[0] file = open(filename, "rb")  pcaphdrlen = 24 pkthdrlen=16 pkthdrlen1=14 iphdrlen=20 tcphdrlen=20 stdtcp = 20 total = 0 pos = 0 start_seq = 0 end_seq = 0 cnt = 0 # Read 24-bytes pcap header data = file.read(pcaphdrlen) (tag, maj, min, tzone, ts, ppsize, lt) = struct.unpack("=L2p2pLLLL", data) # 具體的LinkType細節,請看: # http://www.winpcap.org/ntar/draft/PCAP-DumpFileFormat.html#appendixBlockCodes if lt == 0x71:  pkthdrlen1 = 16 else:  pkthdrlen1 = 14 ipcmp = 0 # Read 16-bytes packet header data = file.read(pkthdrlen) while data:  (sec, microsec, iplensave, origlen) = struct.unpack("=LLLL", data)  # read link  link = file.read(pkthdrlen1)    # read IP header  data = file.read(iphdrlen)  (vl, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr) = struct.unpack(">ssHHHssHLL", data)  iphdrlen = ord(vl) & 0x0F   iphdrlen *= 4  # read TCP standard header  tcpdata = file.read(stdtcp)   (sport, dport, seq, ack_seq, pad1, win, check, urgp) = struct.unpack(">HHLLHHHH", tcpdata)  tcphdrlen = pad1 & 0xF000  tcphdrlen = tcphdrlen >> 12  tcphdrlen = tcphdrlen*4    if direction == 'out':   ipcmp = saddr  else:   ipcmp = daddr  if ipcmp == ip32:   cnt += 1   total += tot_len   total -= iphdrlen + tcphdrlen   if start_seq == 0# BUG?    start_seq = seq   end_seq = seq  # skip data  skip = file.read(iplensave-pkthdrlen1-iphdrlen-stdtcp)  # read next packet  pos += 1  data = file.read(pkthdrlen) # 打印出實際傳輸的位元組數,以及本應該傳輸的位元組數 print pos, cnt, 'Actual:'+str(total),  'ideal:'+str(end_seq-start_seq)

很簡單吧!懂python的人都會嘲笑我!
        其實,在我看pcap檔案格式之前,我曾經一直以為pcap檔案是由類似ASN.1組織的,但是看了以後卻發現不是,也是挺失望的。我之所以失望是因為,看起來以上描述的這種pcap不能描述除了資料包之外的更多東西,它事實上並不是自描述的,它是一種固定長度格式的檔案結構,雖然處理起來很快,但是卻十分不靈活不易擴充套件!徹底的自描述結構就是ASN.1!
......
我們來看一個例子。隨便抓一個TCP包獲得test.pcap檔案,用UE開啟這個pcap,請自行腦補!如果你真的理解了pcap的檔案組織形式,那麼請認真分析,如果不,請理解透徹而不要腦補!


執行python指令碼pcap-parser.py,我們一無所獲,因為這只是包含一個純ACK包的pcap,沒有攜帶任何資料,而python指令碼旨在得到TCP資料流實際傳輸的資料量,因此我們不得不抓取一個攜帶TCP流量的pcap檔案,而這非常簡單。
        兩臺虛擬機器A,B互聯,A啟動httpd,B上執行wget下載一個檔案,同時設定丟包率以獲得額外的重傳資料量。執行:
pcap-parser.py ./testTCP.pcap 192.168.44.129 out
我們得到了以下結果:
...請自行執行獲取
肉眼計算後發現結果是一致的,我認為這個指令碼可用了。然而...
        然而當我用我寫的python指令碼去分析一個tshark抓取的資料包的時候,發現解析錯了,這個時候魔術字就起作用了,我用UE殘忍地打開了這個pcap檔案,結果呢?

魔術字都是錯的!於是上wireshark網站,知道了這是一個pcapng這個檔案格式,同時,也知道了pcapng不能向下相容。這是令人悲傷的一件事,但是幸運的是,pcapng檔案格式要比pcap簡單的多,而且,它基本就是類似ASN.1的組織辦法。

       我們發現pcap的檔案格式中,大部分的元描述結構都是固定數量且定長的,以LinkType為例,一次抓包我只能指定一個LinkType,它被記錄在pcap檔案開始的pcap_file_header中,這意味著,我無法同時在乙太網卡和非以太的PPP網絡卡上抓包並同時得到詳細的鏈路層資訊!而pcapng解決了這個問題。

        欲知pcapng如何,且看下篇文字。


附錄

細節1:Cooked Capture與Ethernet

如果說使用tcpdump -i any引數,我們不會看到標準的以太頭資訊,我們看到的是Cooked Capture,而不是Ethernet!關鍵的是,Cooked Capture描述的元資訊長度是16位元組而不是Ethernet的14位元組。以下是Cooked Capture的頭示例:



這個資訊可以通過LinkType來獲取。為什麼會有這種Cooked Capture型別的資料包?因為抓包工具在-i any的情況下,無法用一種統一的方式來處理鏈路層的長度,比如很多協議,內部又區分了很多的子協議,其協議頭的長度根據應用層而定,這是核心在抓包層面所處理不了的。pcap檔案中,只能在一處指定LinkType,那就是檔案頭之後的pcap_pkthdr,如果說我指定-i eth0 -i lo -i ppp0 -i tun0,那就徹底沒轍了!幸運的是,如果使用pcapng格式來儲存抓包檔案,那就可以針對這些網絡卡區別對待了,每一個網絡卡抓到的包都會關到一個LinkType,你會更輕鬆處理鏈路層,不過,大多數人不在乎鏈路層,也不在乎IP,更多人在乎的是TCP。

細節2:時鐘跳變

作為一個pcap檔案寫的練習,這裡有一個關於時鐘跳變現象重現的例子。
        我們抓包發現了一個奇怪的現象,那就是從客戶端抓包上看,中間間隔了幾十秒沒有收到任何資料,在服務端抓包看來一切正常,總共的資料傳輸時間也就十幾秒,這是怎麼回事呢?
        當即判斷是客戶端抓包時發生了時鐘的跳變,比如時鐘突然後跳了40秒,為了重現這個現象,我拿一個真實的普通正常的TCP下載為例,在收發第900個數據包的時候以及以後,資料包描述頭裡的時間戳欄位統一加上40秒,看看是什麼效果...程式碼非常簡單:
       if pos > 900:               data = struct.pack("=LLLL", sec+40, microsec, iplensave, origlen)        file_out.write(data)
然後就重現了這個現象:



           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述