IM通信協議逆向分析、Wireshark自定義數據包格式解析插件編程學習


相關學習資料

http://hi.baidu.com/hucyuansheng/item/bf2bfddefd1ee70ad68ed04d
http://en.wikipedia.org/wiki/Instant_Messaging_and_Presence_Protocol
https://www.trillian.im/impp/
http://en.wap.wikipedia.org/wiki/Presence_and_Instant_Messaging
http://zh.wikipedia.org/wiki/XMPP
http://xmpp.org/
http://blog.csdn.net/xutaozero21/article/details/4873439
http://searchdomino.techtarget.com/definition/SIMPLE
http://zh.wikipedia.org/wiki/SIMPLE
http://datatracker.ietf.org/wg/simple/
http://qing.blog.sina.com.cn/tj/7f1e5f5333001g7p.html
http://www.wireshark.org/docs/wsdg_html_chunked/ChapterDissection.html
ftp://ftp.man.szczecin.pl/pub/security/packet-capture/wireshark/docs/developer-guide-a4.pdf
http://www.360doc.com/content/11/1117/17/8151417_165252820.shtml 
http://blog.csdn.net/guoqin863/article/details/9088757
http://www.cnblogs.com/hnrainll/archive/2012/06/17/2552943.html
http://my.oschina.net/pkjason/blog/146057

 

目錄

1. IM通信協議分析簡介
2. IM通信軟件通信協議逆向分析
  1) 源代碼逆向
  2) 通過嗅探數據包觀察數據包格式
3. 自定義格式數據包解析
4. 針對.pcap文件進行應用層自定義協議分析

 

1. IM通信協議分析簡介

我們在進行(中間人)嗅探攻擊的時候,經常會遇到使用自定義通訊協議的IM通信數據包,這類數據包對我們、或者wireshark來說都是一段"毫無意義的亂碼",要識別這類數據包,就必須學習自定義IM通信協議的分析原理。
之所以會出現IM即時通信協議,是因為以下原因

1. 通訊終端間交互如何處理?
2. 服務器間交互如何處理?
3. 終端同服務器間交互如何處理?
4. 服務器都扮演哪些角色?
5. 終端能力差異如何處理?
6. 通訊錯誤、安全性問題如何應對?

IM有四種協議
1. IMPP: 即時信息和存在協議(IMPP Instant Messaging And PresenceProtocol)
2. PRIM: 存在和即時信息協議(PRIM Presence and Instant Messageing Protocol)
3. XMPP: 可擴展消息與存在協議XMPP(Extensible Messageing and Presence Protocol)
4. SIMPLE: SIP即時消息和存在擴展協議SIMPLE(SIP for Instant Messaging and Presence Leveraging Extensions): 針對即時通訊和空間平衡擴充目的

0x1: IMPP(Instant Messaging And PresenceProtocol)

IMPP主要定義必要的協議和數據格式,用來構建一個具有空間接收、發布能力的即時信息系統。它包括的草案RFC有

1. 2000-02 RFC 2778: 針對站點空間和即時通訊的模型,它是一個資料性質的草案,定義了所有presence和IM服務的原理
2. 2000-02 RFC 2779: 針對即時通訊/空間協議的最小需求條件
3. 2002-07 RFC 3339: 定義了在Internet上傳輸的日期格式
4. 2004-08 RFC 3859: 定義了服務空間條款
5. 2004-08 RFC 3860: 定義了信息空間條款
6. 2004-08 RFC 3861: 定義了信息和服務空間的地址解析規范
7. 2004-08 RFC 3862: 定義了數據交換的條款
8. 2004-08 RFC 3863: 定義了數據格式 

IMPP是一個比較老的協議標准,更多的相關資料請參閱

https://www.trillian.im/impp/

0x2: PRIM(Presence and Instant Messageing Protocol)

存在和即時消息協議(PRIM)是一個早期建議的IETF標准的協議進行即時通訊。它是XMPP、SIMPLE協議的前身,現在已經不再使用。這個標准最早是在"IETF Request for Comments",即RFC 2778中提出

http://en.wap.wikipedia.org/wiki/Presence_and_Instant_Messaging

0x3: XMPP(Extensible Messageing and Presence Protocol)

XMPP與IMPP、PRIM、SIP(SIMPLE)合稱四大IM協議主流,在此4大協議中,XMPP是最靈活的。
XMPP的關鍵特色

1. 開放
XMPP協議是自由、開放、公開的,並且易於了解。而且在客戶端、服務器、組件、源碼庫等方面,都已經各自有多種實現
2. 標准
互聯網工程工作小組(IETF)已經將Jabber的核心XML流協議以XMPP之名,正式列為認可的實時通信及Presence技術。而XMPP的技術規格已被定義在RFC 3920及RFC 3921。任何IM供應商在遵
循XMPP協議下,都可與Google Talk實現連接
3. 高可用性 第一個Jabber(現在XMPP)技術是Jeremie Miller在1998年開發的,現在已經相當穩定。數以百計的開發者為XMPP技術而努力。今日的互聯網上有數以萬計的XMPP服務器運作著,並有數以百
萬計的人們使用XMPP實時傳訊軟件。
4. 分散式的 XMPP網絡的架構和電子郵件十分相像;XMPP核心協議通信方式是先創建一個stream,XMPP以TCP傳遞XML數據流,沒有中央主服務器。任何人都可以運行自己的XMPP服務器,使個人及組織能夠
掌控他們的實時傳訊體驗。有點類似P2P的基本思想
5. 安全 任何XMPP協議的服務器可以獨立於公眾XMPP網絡(例如在企業內部網絡中),而使用SASL及TLS等技術的可靠安全性,已內置於核心XMPP技術規格中。要記住的是,XMPP是一個工作在應用層上的
協議,在下層可以使用TLS等安全傳輸協議來保證信道的安全性
6. 可擴展 XML命名空間的威力可使任何人在核心協議的基礎上建造定制化的功能。這得益於XML協議本身的高可擴展性,為了維持通透性,常見的擴展由XMPP標准基金會 7. 良好的應用彈性 XMPP除了可用在實時通信的應用程序,還能用在: 1) 網絡管理 2) 內容供稿 3) 協同工具 4) 文件共享 5) 游戲 6) 遠程系統監控 3. 使用XML流 XMPP協議的方式被編碼為一個單一的長的XML文件,因此無法提供修改二進制數據。因此, 文件傳輸協議一樣使用外部的HTTP。如果不可避免,XMPP協議還提供了帶編碼的文件傳輸的所有數
據使用的Base64。至於其他二進制數據加密會話(encrypted conversations)或圖形圖標(graphic icons)以嵌入式使用相同的方法。

XMPP網絡是基於服務器的(即客戶端之間彼此不"直接"交談),但是也是分散式的(服務器可以是分散式的)。不像AOL實時通或MSN Messenger等服務,XMPP沒有中央官方服務器,任何人都可以在自己的網域上運行XMPP服務器。

Jabber識別符(JID)是用戶登錄時所使用的賬號,看起來通常像一個電子郵件地址,如
someone@example.com。前半部分為用戶名,后半部分為XMPP服務器域名,兩個字段以@符號區隔

假設朱麗葉(juliet@capulet.com)想和羅密歐(romeo@montague.net)通話,他們兩人的賬號分別在Capulet.com及Montague.net的服務器上。當朱麗葉輸入信息並按下傳送鈕之后,一連串的事件就發生了:

1. 朱麗葉的"XMPP客戶端"將她的信息傳送到"Capulet.com XMPP服務器"
2. "Capulet.com XMPP服務器"打開與"Montague.net XMPP服務器"的連接
3. "Montague.net XMPP服務器"將信息寄送給羅密歐。如果他目前不在在線,那么存儲信息以待稍后寄送

羅密歐與朱麗葉兩人的XMPP服務是由兩家不同的業者(對應兩個不同的XMPP服務器)所提供的,而他們彼此傳訊時,不須擁有對方"服務器"的賬號,也不須成為對方業者的會員。也就是說,中間的傳輸過程、服務器轉接對XMPP用戶來說是透明的,XMPP用戶可以認為是在進行"點對點傳輸"

XMPP協議格式

基本的jabber客戶端必須實現以下標准協議(XEP-0211)

1. RFC3920 Core
http://tools.ietf.org/html/rfc3920
2. RFC3921 Instant Messaging and Presence
http://tools.ietf.org/html/rfc3921
3. EP-030 Service Discovery
http://www.xmpp.org/extensions/xep-0030.html
4. XEP-0115 Entity Capabilities
http://www.xmpp.org/extensions/xep-0115.html

基本的jabber服務器必須實現以下標准協議(XEP-0212)

1. RFC3920 Core
http://tools.ietf.org/html/rfc3920
2. RFC3921 Instant Messaging and Presence
http://tools.ietf.org/html/rfc3921
3. XEP-030 Service Discovery
http://www.xmpp.org/extensions/xep-0030.html

通信數據包格式

1. 注冊
XEP-0077 In-Band Registration: http://www.xmpp.org/extensions/xep-0077.html
2. 登錄
XEP-0020 Software Version: http://www.xmpp.org/extensions/xep-0092.html
3. 好友列表
    3.1) 獲取好友列表
    XEP-0083 Nested Roster Groups: http://www.xmpp.org/extensions/xep-0083.html
    3.2) 存儲好友列表
    XEP-0049 Private XML Storage: http://www.xmpp.org/extensions/xep-0049.html
    3.3) 備注好友信息
    XEP-0145 Annotations: http://www.xmpp.org/extensions/xep-0145.html
4. 存儲書簽
XEP-0048 Bookmark Storage: http://www.xmpp.org/extensions/xep-0048.html
5. 好友頭像
    5.1) XEP-0008 IQ-Based Avatars: http://www.xmpp.org/extensions/xep-0008.html
    5.2) XEP-0084 User Avatar: http://www.xmpp.org/extensions/xep-0084.html
    5.3) XEP-0054 vcard-temp: http://www.xmpp.org/extensions/xep-0054.html
6. 用戶狀態
RFC-3921 Subscription States: http://www.ietf.org/rfc/rfc3921.txt
7. 文本消息
    7.1) 在線消息
    7.2) 離線消息
        7.2.1) XEP-0013 Flexible Offline Message Retrieval: http://www.xmpp.org/extensions/xep-0013.html
        7.2.2) XEP-0160 Best Practices for Handling Offline Messages: http://www.xmpp.org/extensions/xep-0160.html
        7.2.3) XEP-0203 Delayed Delivery: http://www.xmpp.org/extensions/xep-0203.html
    7.3) 聊天狀態通知
    XEP-0085 Chat State Notifications: http://www.xmpp.org/extensions/xep-0085.html
    7.4) 群組聊天
    XEP-0045 Multi-User Chat: http://www.xmpp.org/extensions/xep-0045.html
8. 文件傳輸
    8.1) XEP-0095 Stream Initiation: http://www.xmpp.org/extensions/xep-0095.html
    8.2) XEP-0096 File Transfer: http://www.xmpp.org/extensions/xep-0096.html
    8.3) XEP-0065 SOCKS5 Bytestreams: http://www.xmpp.org/extensions/xep-0065.html
    8.4) XEP-0215 STUN Server Discovery for Jingle: http://www.xmpp.org/extensions/xep-0215.html
    8.5) RFC-3489 STUN: http://tools.ietf.org/html/rfc3489
9. 音視頻會議
    9.1) XEP-0166 Jingle: http://www.xmpp.org/extensions/xep-0166.html#negotiation
    9.2) XEP-0167 Jingle Audio via RTP: http://www.xmpp.org/extensions/xep-0167.html
    9.3) XEP-0176 Jingle ICE Transport: http://www.xmpp.org/extensions/xep-0176.html
    9.4) XEP-0180 Jingle Video via RTP: http://www.xmpp.org/extensions/xep-0180.html#negotiation
    9.5) XEP-0215 STUN Server Discovery for Jingle: http://www.xmpp.org/extensions/xep-0215.html
    9.6) RFC-3489 STUN: http://tools.ietf.org/html/rfc3489
10. 用戶查詢
XEP-0055 Jabber Search: http://www.xmpp.org/extensions/xep-0055.html
11. 基礎功能
    11.1) 協議數據交互
    XEP-0004 Data Forms: http://www.xmpp.org/extensions/xep-0004.html
    11.2) jabber-RPC
    XEP-0009 Jabber-RPC: http://www.xmpp.org/extensions/xep-0009.html
    11.3) 功能協商
    XEP-0020 Feature Negotiation: http://www.xmpp.org/extensions/xep-0020.html
    11.4) 服務發現
    XEP-0030 Service Discovery: http://www.xmpp.org/extensions/xep-0030.html
    11.5) 會話建立
        11.5.1) XEP-0116 Encrypted Session Negotiation: http://www.xmpp.org/extensions/xep-0116.html
        11.5.2) XEP-0155 Stanza Session Negotiation: http://www.xmpp.org/extensions/xep-0155.html
        11.5.3) XEP-0201 Best Practices for Message Threads: http://www.xmpp.org/extensions/xep-0201.html

0x4: SIMPLE(SIP for Instant Messaging and Presence Leveraging Extensions)

SIMPLE(SIP for Instant Messaging and Presence Leveraging Extensions),它有如下特點:

1. 是一個基於sip協議的即時消息通信協議族,此協議
2. 由IETF的IMPP工作組提出,是目前為止制定的較為完善的一個
3. SIMPLE和XMPP兩個協議,都符合RFC2778和RFC2779
4. SIMPLE計划利用SIP來發送presence信息
5. SIP是IETF中為終端制定的協議,一般考慮用在建立語音通話中,一旦連接以后,依靠如實時協議(RTP)來進行實際上的語音發送。但SIP不僅僅能被用在語音中,也可以用於視頻
6. SIMPLE被定義為建立一個IM進程的方法

更多相關資料,請參閱

http://searchdomino.techtarget.com/definition/SIMPLE
http://zh.wikipedia.org/wiki/SIMPLE
http://datatracker.ietf.org/wg/simple/

 

 

2. IM通信軟件通信協議逆向分析

了解了基本的IM通信協議之后,我們接下來學習一下怎么對現有的IM軟件的通信協議進行逆向分析,一般來說,IM通信軟件使用的協議有如下特點:

1. 工作在基於TCP或者UDP的應用層
2. 自定義協議數據包格式
3. 使用現有的加密、壓縮的算法,或者使用自定義的算法庫

要分析軟件的通信協議,就必須要采用"逆向分析"的方法,這里的"逆向"包括

1. 源代碼逆向
直接對軟件本身進行二進制逆向分析,從源代碼的角度直接分析出數據包的"組裝過程",從而得到目標軟件使用的協議格式。這是最直接、有效的方法,但是難度較大,如果目標軟件采用了反調試
等手段的話
2. 通過嗅探數據包觀察數據包格式 使用使用嗅探工具(如wireshark)在目標IM軟件的通信鏈路上進行抓包,基於"控制變量"的思想,通過觀察不同"輸入值"對應的數據包內容,以此來逆向"推測"(只能是推測)出目標IM軟件使用的
通信協議,這種方法雖然實施難度小,但工作量較大

本次實驗的材料為"XDSEC2013 Exploit 5"的Chat.exe

http://pan.baidu.com/s/1eQw0hbW

0x1: 通過嗅探數據包觀察數據包格式

通過"控制變量"的思路,來逆向分析協議的數據包格式

1. 尋找當前軟件和通信相關的所有"輸入點"
第一步的目的收集所有的變量點(既然我們要控制變量),尋找所有可能使數據包變化的輸入點。對於本文中我們的程序來說,有如下幾點:
    1) 用戶名
    2) 監聽端口
    3) 對方端口
    4) 對方IP地址
    5) 發送的消息內容
    6) 發送的時間(這是一個非UI的輸入點)
2. 使用"控制變量技術"進行協議分析
即保持1) 用戶名逐位變化,其他1)..6)輸入點都不變(這點很重要,只有不變才能體現控制變量的思想),觀察數據包的變化。對2)也是如果,依次類推,直到把所有的條件都嘗試過一遍,
得到一張笛卡兒積控制變量表

下面開始實驗過程

1. "用戶名"在協議格式中的作用fuzz測試
我們以"用戶名"這個變量輸入變動輸入值,其他的維度的變量全部固定住
    1) a: [XIU<b@79442:2685&jflrt(
    2) A: [XIU<B@79442:27:6&jflrt(
    3) b: [XIU<c@79442:28<:&jflrt(
    4) B: [XIU<C@79442:2:5<&jflrt(
    5) Little: [XIU<MizyphB246653710:(lhnmo*
    6) a: [XIU<b@79443156>:&jflrt(
    7) Little: [XIU<MizyphB246653843=(lhnmo*
    8) Little: [XIU<MizyphB2466538647(lhnmo*
    8) a: [XIU<b@7944316<65&jflrt(
從這組控制變量實驗中,我們可以大致得出以下猜想:
    1) 數據包頭部的"[XIU<"這5個ASCII字符是一個獨立的部分,它應該包含了這個數據包的某些"描述信息"
    2) 1)組數據和6)組數據的條件全部都一樣(除了發送時間,它們是兩組獨立的實驗),但是發現"[XIU<"后面的6個ASCII字符"b@7944"是一樣的,再后面就不一樣了,我們可以得出以下猜想
        2.1) "[XIU<"后面的6個ASCII字符只和用戶名有關
        2.2) "[XIU<"后面的6個ASCII字符"b@7944"再之后的6個ASCII字符應該是和時間有關(至少時間這個維度的變量參與了運算)
    3) 5)組數據和6)組數據的全部條件都一樣(除了用戶名、發送時間),這兩個數據包的最后一部分"(lhnmo*"都是相同的,同樣的情況發生在1)組實驗和6)組實驗中
        3.1) 數據包的最后一部分數據只和發送的消息內容有關
        3.2) 但是,用戶名的長度似乎決定了數據包中間部分的長度和結果

2. 消息內容在協議格式中的作用fuzz測試
我們以"消息內容"這個變量輸入變動輸入值,其他的維度的變量全部固定住
    1) hello: [XIU<b@7944318?9<&jflrt(
    2) world: [XIU<b@7944318?<4&yprri(
    3) a:      [XIU<b@7944319697&c%
    4) hello: [XIU<b@79443196=7&jflrt(
    5) aa:    [XIU<b@794431997<&cb$
    6) aaa:      [XIU<b@79443199<;&cba*
從這組控制變量實驗中,我們進一步得出以下猜想:
    1) 1)組實驗和4)組實驗的條件全部都一樣(除了發送時間),發現數據包的中間部分不同,可以證明在第一次實驗中的猜想是正確的,這部分和時間有關
    2) 3)、5)、6)組實驗中,我們逐個增大消息的長度,而數據包中代表消息內容的最后那部分的數據長度也依次增加,我們可以知道目標軟件使用的加密算法是一個"類似分組"的算法,因為
分組算法的特性是"密文長度會隨着明文的增大而增大" 3) 從以上的實驗中,我們還可以得出一個新的猜想,目標軟件的協議格式是"分段"的,有明顯的區域性,而且每塊區域之間的關聯性不大

這種猜想雖然沒有確定性的證據,但是對我們進一步分析協議格式卻意義重大,在我們真正開始進行代碼逆向之前,我們可以根據fuzz結果在腦海中建立起對這個程序的協議的框架性的概念,在實驗的過程中,我們需要牢記的幾點是:

1. 尊重實驗數據的結果,我們在fuzz實驗的過程中,要隨時根據實驗結果不斷"修正"我們對當前協議格式的"猜想",在實驗的最開始,我們一定會根據當前的分析結果迅速的在腦海里建立起對
目標協議格式的一個框架性猜想,我們的猜想也許會非常的"完美",符合之前的所有情況,但是,我們要知道這有可能是我們的測試范圍還不夠,因為: 1) 時間延時因素 2) 目標程序的算法本身攜帶有某種隨機因素 3) 測試覆蓋度不夠 等等原因,當一個新的情況發生,並且和我們現有的猜想沖突時,我們要立刻重新思考我們的框架性猜想,對它進行重組、修改,最終的目標是"完全"符合現有的所有測試數據(一定要完全) 2. 在進行控制變量實驗的時候要注意"平行對比",一個好的做法是將輸入變量按照某個"模式"(遞增、遞減、倍增)進行變化,這樣,更容易獲得目標協議的模式表現

在進行了初步的fuzz測試之后,我們帶着猜想和感官認識進入代碼逆向的步驟,這一步,我們需要驗證我們的猜想,獲取目標協議算法、格式的完整原理

0x2: 源代碼逆向

依然使用OD(動態)+IDA(靜態)調試方法

先運行一下程序,收集一些基本信息

是一個C程序,那基本和WSsocket有關了。=

查一下WSsocket對應的API

WSASend
http://msdn.microsoft.com/en-us/library/windows/desktop/ms742203(v=vs.85).aspx

send
http://msdn.microsoft.com/en-us/library/windows/desktop/ms742203(v=vs.85).aspx

recv
http://msdn.microsoft.com/en-us/library/windows/desktop/ms740121(v=vs.85).aspx

WSArecv
http://msdn.microsoft.com/en-us/library/windows/desktop/ms741688(v=vs.85).aspx

recvfrom
http://msdn.microsoft.com/en-us/library/windows/desktop/ms740120(v=vs.85).aspx

用recvfrom把目標程序斷下來,這個程序是用recvfrom來接收socket的UDP數據包的,繼續跟蹤,找到解密函數

CPU Disasm
Address    Hex dump               Command                                    Comments
0068B706   |.  E8 B694FEFF        call    00674BC1
0068B70B   |.  83C4 08            add     esp, 8
到這個函數之后,密文已經解密出來了

根據這個地址使用IDA的Xray反匯編進行代碼逆向

我們可以得到程序使用的解密算法,使用C語言描述如下:

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    const int seed[] = {6,5,4,3,2,1,0};
    int len;
    int i;
    char msg[100];
    printf("Please Input Your Msg: "); 
    scanf("%s", msg);  
    len = strlen(msg);
    for(i = 0 ; i < len ;i++)
    {
        msg[i] = msg[i] - seed[i%7];
    } 
    printf("\nThe result is: %s\n", msg);

    return 0;
}

相對的,我們也可以用同樣的方法對sendto()方法進行斷點,從而找到目標軟件的加密算法

#include "stdafx.h"

int _tmain(int argc, _TCHAR* argv[])
{
    const int seed[] = {6,5,4,3,2,1,0};
    int len;
    int i;
    char msg[100];
    printf("Please Input Your Msg: "); 
    scanf("%s", msg);  
    len = strlen(msg);
    for(i = 0 ; i < len ;i++)
    {
        msg[i] = msg[i] + seed[i%7];
    } 
    printf("\nThe result is: %s\n", msg);

    return 0;
}

可以看到,這個程序只是一個簡單的"+-"的循環操作加密方式,同時,理解了加密方式之后,也驗證了我們在fuzz階段的猜想,因為所有的明文數據包都是以"USER:"這5個ASCII字符開頭的,所以加密后的結果永遠是"[XIU<"。

同時,我們也得到了目標軟件的協議格式:

USER:Little@1380897758#hello$
1. 消息頭部
USER:
2. 發送者的用戶名
Little
3. 發送者用戶名"定界符"
@
4. 發送方發送消息時的UNIX時間戳
1380897758
5. 發送方發送消息時的UNIX時間戳的"定界符"
#
6. 消息內容
hello
7. 消息內容"定界符"
$

至此,我們通過代碼逆向獲得了目標協議的格式、加密算法,最終的結果和我們當初的猜想大體一致

 

 

3. 自定義格式數據包解析

wireshark采用插件技術,程序員開發一種新的協議分析模塊的時候不需要了解所有的代碼,作為一個協議解析器,其要完成的工作是將數據包中,解析器所針對的協議部分的各個字段的信息,進行詳細地呈現。

觀察wireshark 的界面程序,在顯示一個數據包的詳細內容的窗口中,是以一個樹形的結構來將數據包划分成各層協議,並展示各部分的含義。同時,在數據包列表主窗口中,還顯示的有各個數據包的概覽信息,並可以通過相關的過濾規則進行篩選顯示。

所以,解析器的核心工作就在於數據包詳細內容窗口部分的樹形結構的維護,並結合過濾器、數據包列表等部分,進行篩選與信息的呈現。
wireshark允許以插件的形式動態地插入協議解析模塊,有三種方式為其添加協議解析(protocol dissection)功能

1. 內置解析器(Build-In)
2. 動態鏈接庫形式(DLL)的插件解析器
3. Lua或Python語言的插件解析器(Script解析器)

本文以基於C語言開發DLL形式的插件解析器為目標進行學習,首先,我們要知道生成的插件DLL需要提供兩個對外的接口就可以了

1. proto_register()接口: 注冊解析器,注冊協議的名稱,過濾字符串,及其它相關所需的結構。
2. proto_reg_handoff()接口: 用來注冊解析器的解析句柄,處理協議的解析及顯示工作。

在Wireshark中有一個腳本專門來發現開發者定義的類似proto_reg_handoff_xxx和proto_register_xxx這樣的注冊函數名,然后"自動"生成調用這些注冊函數的代碼。
wireshark啟動時:

1. wireshark會加載所有$installdir/plugins/$buildversion/*.dll
2. 依次調用每個DLL導出的這兩個函數
    1) proto_register(): dissector插件的注冊
    2) proto_reg_handoff(): dissector協議解析器的初始化工作  

0x1: 目標協議字段格式分析
編寫自定義協議解析器的第一步就是要了解目標協議的字段格式,這里我們采用DEMO程序進行實驗學習

client.c

#include <WinSock2.h>
#include <stdio.h>

#define  UDP_PORT_FOO  9877

/*
目標協議的格式為: type|flags|seqno|ipaddr
*/
struct proto_foo
{
    UINT8  type;
    UINT8  flags;
    UINT16 seqno;
    UINT32 ipaddr;
};

int main(int argc, char** argv)
{
    SOCKET sockfd;
    SOCKADDR_IN addr;

    WORD dwVersion = MAKEWORD(2, 2);
    WSAData wsaData;
    WSAStartup(dwVersion, &wsaData);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(UDP_PORT_FOO);
    //填寫客戶端主機的IP地址
    addr.sin_addr.s_addr = inet_addr("172.21.11.243"); 

    proto_foo data;
    //填寫客戶端主機的IP地址
    data.ipaddr = inet_addr("172.21.11.243");
    INT16 seq = 1;

    for(;;)
    { 
        srand((unsigned int)time(NULL));
        data.type = rand() % 3 + 1;
        data.flags = rand() % 4 + 1;
        if(data.flags == 3)
        {
            data.flags = 4;
        } 
        data.seqno = htons(seq++);

        sendto(sockfd, (const char*)&data, sizeof(proto_foo), 0, (SOCKADDR*)&addr, sizeof(addr));
        Sleep(1000);
    } 
    closesocket(sockfd); 
    WSACleanup();

    return 0;
}

server.c

#include <WinSock2.h>
#include <stdio.h>

#define  UDP_PORT_FOO  9877

int main(int argc, char** argv)
{
    SOCKET sockfd;
    SOCKADDR_IN addr;

    WORD dwVersion = MAKEWORD(2, 2);
    WSAData wsaData;
    WSAStartup(dwVersion, &wsaData);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(UDP_PORT_FOO);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(sockfd, (SOCKADDR*)&addr, sizeof(addr));

    char buff[16];
    int n = 0;
    for(;;)
    {
        n = recvfrom(sockfd, buff, 16, 0, NULL, NULL);
        buff[n] = '\0';
        puts(buff);
    } 
    closesocket(sockfd); 
    WSACleanup();

    return 0;
}

目標協議的格式為:

type|flags|seqno|ipaddr
1) packet type(8 bits)
    1.1) 1: 初始化
    1.2) 2: 終止
    1.3) 3: 數據
2) flags:(8 bit)
    2.1) 0x01: 開始packet
    2.2) 0x02: 結束packet
    2.30 0x04: 優先packet
3) seq number(16 bits)
4) ip地址

0x2: wireshark協議解析器代碼編寫
編寫插件第一步是創建目錄,由於是插件方式,新建的目錄需要在wireshark的源碼目錄的plugins目錄下,以需要解析的協議名稱命名,如"foo"。創建好目錄之后,就可以在目錄中建立源碼文件,新建自己的協議解析文件,可以將每個解析器放在各自的單獨的.c文件中,每個.c文件中最好包含前文提到的三大部分,推薦為packet-foo.c

packet-foo.c

#include "config.h"
#include <epan/packet.h>

#define FOO_PORT 9877

static int proto_foo = -1;
static int hf_foo_pdu_type = -1;
static int hf_foo_flags = -1;
static int hf_foo_seqno = -1;
static int hf_foo_ip = -1;
static gint ett_foo = -1;

static const value_string pkt_type_names[] = 
{
    {1, "Initilize"},
    {2, "Terminate"},
    {3, "Data"},
    {0, NULL}
};

#define FOO_START_FLAG  0x01
#define FOO_END_FLAG        0x02
#define FOO_PRIOR_FLAG  0x04


static int hf_foo_start_flag    = -1;
static int hf_foo_end_flag      = -1;
static int hf_foo_prior_flag    = -1;


/*
這個函數就是我們之前在proto_reg_handoff_foo綁定的協議處理函數,此函數用於解析交給它的packets
1) tvbuff_t: packets數據緩存
2) packet_info: 包含有關協議的一般數據,這也是在wireshark的UI界面上將要顯示出的數據,我們應該在函數中更新信息
3) proto_tree: 參數是細節解析發生的地方。
*/
static void dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
{   
    //獲取協議的前8個字節(表示數據包類型)
    guint8 packet_type = tvb_get_guint8(tvb, 0);
    
    //設置我們協議的文本,以示用戶可以看到協議被識別了
    col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
    //清除INFO列中的所有數據,如果它正在被顯示的話
    col_clear(pinfo->cinfo,COL_INFO);
    col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s", val_to_str(packet_type, pkt_type_names, "Unknown (0x%02x)"));
    
    //向數據包子樹中(wireshark中點擊"數據包詳情"")
    if(tree)
    {
        /*
        proto_item *proto_tree_add_item(proto_tree *tree, const int hfindex, tvbuf_t *tvb, const gint start,gint length,const guint encoding);
        往tree里添加新的節點
        1) proto_tree: 要添加的子樹tree
        2) hfindex: 指明和此結點關聯的協議字段
        3) tvbuf_t tvb: 數據包內容的指針
        4) start:; 選中協議字段后,原始數據包中高亮部分相對起始數據包的偏移
        5) Length: 需要高亮的字節數,為-1,表示一直到數據包結束,即高亮剩余全部
        6) Encoding: 表示數據包中的內容在協議字段中處理的時候,是否需要字節序的轉換
        返回值為添加的結點
        */
        proto_item* ti = NULL;
        proto_tree* foo_tree = NULL;
        gint offset = 0;
        
        ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
        proto_item_append_text(ti, ", Type %s", val_to_str(packet_type, pkt_type_names, "Unknown (0x%02x)"));
        foo_tree = proto_item_add_subtree(ti, ett_foo);
        
        proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(foo_tree, hf_foo_start_flag, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(foo_tree, hf_foo_end_flag, tvb, offset, 1, ENC_BIG_ENDIAN);
        proto_tree_add_item(foo_tree, hf_foo_prior_flag, tvb, offset, 1, ENC_BIG_ENDIAN);
        offset += 1;
        proto_tree_add_item(foo_tree, hf_foo_seqno, tvb, offset, 2, ENC_BIG_ENDIAN);
        offset += 2;
        proto_tree_add_item(foo_tree, hf_foo_ip, tvb, offset, 4, ENC_BIG_ENDIAN);
        offset += 4;
    }
}


/*
proto_register_Chat()是開發wireshark的DLL插件必須定義的導出函數之一,wireshark在啟動的時候會自動調用DLL中的這個函數來進行"協議解碼器"
的注冊,這也是很多框架的擴展開發的基本思想(例如PHP的擴展開發)
注冊協議的函數模板為: void proto_register_xxx(void);
且必須以proto_register_XXX 命名,XXX含義為前面提到的協議名(即Chat)
函數無需返回值,也無需參數。
*/
void proto_register_foo(void)
{
    /*
    hf_register_info:
    此結構是一個用於代表協議中用於解析各個字段信息的結構。使用的時候,通常使用數組方式來代表協議中的諸多字段
    我們定義了3個結構,用於表示協議的3個字段,這個數組的結構解釋如下
    1) 元素1: 保存標識的引用變量
    賦值列表中,&hf_foo_pdu_type代表取得hf_foo_pdu_type的地址,hf_foo_pdu_type初值必須為-1,后續在對此數組進行注冊的時候,
    會對hf_chat_user進行賦值,保存此結構單位的標識
    2) 協議字段名稱
    字符串"Type",作為參數傳遞給此結構,代表在詳細信息窗口中,此結構對應的協議字段的名稱信息
    3) 過濾器名稱
    字符串"foo.type"為一過濾字符串。Wireshark允許針對某一協議進行過濾,可以針對某一協議字段進行過濾。即我們可以在Filter中輸入
    foo.type == "Data"的方式來過濾出"數據類型"的數據包
    4) 協議字段類型: 用於標識此協議字段的敏感類型
        4.1) FT_BOOLEAN為一枚舉值,表示敏感的協議字段為布爾型 
        4.2) FT_BYTES: 字節型
        4.3) FT_STRING: 字符串型
        4.4) FT_IPv4: IPv4格式
        4.5) FT_UINT16: INT16長整型
    5) 此字段在詳細信息中數據的進制顯示方式
        5.1) BASE_DEC: 十進制方式顯示
        5.2) BASE_HEX: 二進制方式顯示
        5.3) BASE_NONE: 普通方式
    6) 此協議字段的值對應的顯示內容列表,當目標協議的數據包有不同類型的時候會使用到這個字段,我們用VALS宏來把上表與數據的相應
    部分關聯起來,這樣可以針對不同類型的數據包進行分類
    7) 字段敏感掩碼,類型為整數,通常寫成十六進制
    如:我們需要處理的字段為一個字節的高四位,但是前面第四部分索要處理的部分寫的是一個字節,此處我們就可以填入0xf0,來去掉低四
    位對相關計算的影響
    8) 保留字段: 填寫0、后者NULL均可
    9) HFILL 為一個宏,代表一段固定常用值
    */
    static hf_register_info hf[] = 
    {
        {
            &hf_foo_pdu_type,
            {
                "Type", "foo.type",
                FT_UINT8, BASE_DEC,
                VALS(pkt_type_names), 0x0, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_flags,
            {
                "Flags", "foo.flags",
                FT_UINT8, BASE_HEX, 
                NULL, 0x0, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_start_flag,
            {
                "Start Flag", "foo.flags.start",
                FT_BOOLEAN, 8,
                NULL, FOO_START_FLAG, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_end_flag,
            {
                "End Flag", "foo.flags.end",
                FT_BOOLEAN, 8,
                NULL, FOO_END_FLAG, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_prior_flag,
            {
                "Priority Flag", "foo.flags.prior",
                FT_BOOLEAN, 8,
                NULL, FOO_PRIOR_FLAG, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_seqno,
            {
                "Sequence Number", "foo.seq",
                FT_UINT16, BASE_DEC,
                NULL, 0x0, 
                NULL, HFILL
            }
        },
        {
            &hf_foo_ip,
            {
                "IP Address", "foo.ip",
                FT_IPv4, BASE_NONE,
                NULL, 0x0, 
                NULL, HFILL
            }
        }
    };
    
    /*
    gint指針數組
    通常的定義方式為:
    static gint *ett[] = 
    {
        &ett_xxx
    };
    定義此數組后也需要注冊。注冊此數組的過程,實際上是在注冊一些標識變量的值,故而數組內部是標識變量的地址,即此例中注冊ett,
    實際會注冊ett_xxx,並在ett_xxx中存入標志值,在后文會用此標識
    */
    static gint *ett[] = { &ett_foo };

    /*
    proto_XXX = proto_register_protocol("Protocol full name", "Protocol short name", "protocol filter name");
    其中:
    1) proto_XXX:協議全局標識,用於后續相關的注冊與解析工作,可以簡單理解為一個句柄
    */
    proto_foo = proto_register_protocol (
        "FOO Protocol", /* name       */
        "FOO",      /* short name */
        "foo"       /* abbrev     */
        );
    
    /*
    函數proto_register_field_array(proto_xxx, hf, array_length(hf))必須在注冊協議函數proto_register_protocol 之后調用
    用以保存協議的字段格式
    */
    proto_register_field_array(proto_foo, hf, array_length(hf));

    /*
    通過調用函數proto_register_subtree_array(ett,array_length(ett)),對子樹結構數組進行注冊
    ett數組用於保存協議詳細信息中的樹形子樹節點的信息,僅子樹需要,普通節點不需要。如是否處於展開狀態等
    */
    proto_register_subtree_array(ett, array_length(ett));
}


/*
掛載解析器
掛載解析器,其實就是將我們的解析器掛載到前文提到的樹形節點上。由於掛載后,會從一個解析器切換到我們掛載的解析器,所以,也稱注冊
切換器。注冊過程也就是調用函數:void proto_reg_handoff_xxx(void); 
在此函數內,主要的工作是:生成解析函數的句柄、掛載解析器(注冊切換器)創建一個dissector handle,]
它和foo協議及執行實際解析工作的函數關聯。接下來將此handle與UDP端口號關聯,以便主程序在看到此端口上的UDP數據時調用我們的解析器。
*/
void proto_reg_handoff_foo(void)
{
    //生成解析程序句柄
    static dissector_handle_t foo_handle;
    //獲取解析函數的句柄
    foo_handle = create_dissector_handle(dissect_foo, proto_foo);
    /*
    掛載解析函數句柄
    dissector_add(const char *name,const guint32 pattern,dissector_handle_t dissector);
    由於我們針對TCP 與UDP 協議之上的協議進行解析,所以我們的協議解析器可以通過斷開號來唯一標識
    1) name: 此處我們掛載的解析器需要傳入前面獲取的解析函數句柄。掛載點有點類似我們前面的協議字段過濾規則,如"udp.port"表示過
    濾端口號為FOO_PORT(9877)的數據包。此處同樣的理解方式,name處為過濾字符串udp.port
    2) pattern: FOO_PORT(9877),就可以將我們的解析器掛載到協議樹中。
    可以理解為:告訴wireshark,將name和pattern過濾的結果,交給解析器句柄對應的解析函數來進行解析 
    */
    dissector_add_uint("udp.port", FOO_PORT, foo_handle);
}

0x3: wireshark源代碼編譯
wireshark目前並不支持插件的SDK開發,所以,我們要進行插件的編譯,必須和wireshark的源代碼一起進行編譯

1. 安裝VS1020(不要用VS2008)
2. 安裝Python2.7
3. 安裝Cygwin
在安裝的時候,以下模塊必須選取
    1) Archive/unzip
    2) Devel/bison
    3) Devel/flex
    4) Interpreters/perl
    5) Utils/patch
    6) Web/wget
4. 下載wireshark源代碼
5. 下載編譯要用的Lib庫
http://anonsvn.wireshark.org/wireshark-win32-libs/trunk/packages/ 
下載在Win32上編譯所需要的Lib庫,注意: 把下載后的zip 文件原樣放在C:\wireshark-win32-libs 目錄下即可,不要解壓
6. 編輯config.nmake文件
    1) WIRESHARK_BASE_DIR:
    設置編譯wireshark所需要的庫所在的目錄: 
    !IFNDEF WIRESHARK_LIB_DIR
    !IFDEF WIRESHARK_BASE_DIR
    WIRESHARK_LIB_DIR=$(WIRESHARK_BASE_DIR)\$(PROGRAM_NAME)-$(WIRESHARK_TARGET_PLATFORM)-libs
    !ELSE
    WIRESHARK_LIB_DIR=C:\$(PROGRAM_NAME)-$(WIRESHARK_TARGET_PLATFORM)-libs
    !ENDIF
    !ENDIF
    2) PROGRAM_FILES:
    設置本機程序安裝目錄,默認即可
    3) MSVC_VARIANT
    因為我們使用的是VS2008,所以在這里把值為MSVC2008的那一行前面的#去掉,其余MSVC_VARIANT項保持不變
    4) CYGWIN_PATH
    將其設置為Cygwin的bin目錄,例如C:\Cygwin\bin
    5) PYTHON_VER
    27
    6) PYTHON_DIR
    C:\Python$(PYTHON_VER)
    7) MSVCR_DLL
    (PROGRAM_FILES)\Microsoft Visual Studio 9.0\VC\redist\$(PROCESSOR_ARCHITECTURE)\Microsoft.VC90.CRT\*.*
    8) MAKENSIS
    如果你沒有安裝NSIS 安裝程序制作工具, 用#注釋掉此行
    9) HHC_DIR
    如果沒有安裝HTML Help Workshop(chm 幫助文件制作工具), 注釋掉此行
    10) HHC_EXE
    如果沒有安裝HTML Help Workshop(chm 幫助文件制作工具), 注釋掉此行
    11) INSTALL1_DIR
    如果不想生成GTK1 程序, 用#注釋掉此行
打開VS2008/2010的CMS窗口
pushd C:\Documents and Settings\Administrator\桌面\tools\wireshark_32\source\wireshark-1.11.3
//安裝前驗證
nmake -f Makefile.nmake verify_tools
//下載編譯過程中所需要的庫文件
nmake -f Makefile.nmake setup
//這時,會在wireshark_libs 目錄下下載一些庫文件並解壓完成
//來清除源代碼中用於在其它平台下編譯的文件
nmake -f Makefile.nmake distclean
//編譯
nmake -f Makefile.nmake all

0x4: 對插件代碼進行編譯 

1. 編譯前准備工作
    1) 安裝VS2010(不要用VS2008)
    2) 安裝Cygwin
    http://www.cygwin.com/ 
    3) 安裝python2.7
    https://www.python.org/downloads/  
2. 下載wireshark的源代碼
http://www.wireshark.org/download/src/
整個源碼的目錄結構 
wireshark: wireshark源碼根目錄
    |-epan:        內置解碼器等其他功能模塊目錄
    |-doc:        文檔所在目錄
    |-plugins:    插件所在目錄,scoreboard 目錄即在此
    ..
    tshark.c:    tshark 主程序入口所在文件 
3. 編譯文件准備
在wireshark源碼目錄的plugins目錄下建立foo目錄(我們的自定義協議插件名稱)后,在其中放入如下文件
    1) AUTHORS
    2) COPYING
    3) ChangeLog
    4) CMakeLists.txt
    5) Makefile.am: UNIX/Linux平台下的makefile模板
    6) Makefile.common: 這個文件包含了內置插件所依賴的文件。
    7) Makefile.nmake: 這個文件是Windows平台下WireShark內置插件的makefile
    8) moduleinfo.h: 內置插件的版本信息
    9) moduleinfo.nmake: Windows平台下DLL的版本信息
    10) plugin.rc.in: Windows平台下的DLL資源模板。
這些文件,可以從plugins/gryphon/目錄下拷貝,然后在對其修改
    1) 將makefile.am文件中的gryphon單詞全部替換成foo
    2) makefile.common文件中,需要將自己插件會導出,register_*() 和handoff_*() 的主要的源代碼文件列入,本例中即packet-foo.c,並且.h頭文件本例中不存在
    3) moduleinfo.h文件中, 定義報名、版本號
    4) moduleinfo.nmake文件中,將包名、版本號改變成和前一步相同即可
    5) plugin.rc.in文件是windows下編譯使用的資源文件,用於添加給dll文件的特定信息
    6) 修改makefile.common文件中的PLUGIN_NAME為foo
修改自己插件目錄以外的文件:
    1) plugins/Makefile.am文件中,需要將自己插件所在的目錄(本例中為foo)添加入SUBDIRS變量中
    2) 在最頂層的/Makefile.am中,添加dlopen plugins/foo/foo.la語句到plugin_ldadd中。
    3) 在最頂層的configure.ac文件中,添加plugins/foo/Makefile語句到AC_OUTPUT規則中。
    4) 在epan/Makefile.am文件中添加../plugins/foo/packet-foo.c到plugin_src中。
    5) 在packaging/nsis/Makefile.nmake 文件中,給PLUGINS變量添加../../plugins/foo/foo.dll
    6) 在packaging/nsis/wireshark.nsi文件中,在Dissector Plugins區塊中,給File聲明添加如下語句:
    File "..\..\plugins\foo\foo.dll"  
    7) 修改plugins/Makefile.nmake,在PLUGIN_LIST下添加foo目錄
4. 開始編譯
打開VS2008/2010的CMS窗口
pushd C:\Documents and Settings\Administrator\桌面\tools\wireshark_32\source\wireshark-1.11.3\plugins\foo
nmake -f Makefile.nmake distclean: (刪除其他平台的冗余代碼) 
nmake -f Makefile.nmake all: (編譯插件)

更多細節請參閱

http://qing.blog.sina.com.cn/tj/7f1e5f5333001g7p.html
http://www.wireshark.org/docs/wsdg_html_chunked/ChapterDissection.html
ftp://ftp.man.szczecin.pl/pub/security/packet-capture/wireshark/docs/developer-guide-a4.pdf

 

 

4. 針對.pcap文件進行應用層自定義協議分析
我們繼續探究自定義數據格式的自動化解析工作,回想一下我們之前學習的wireshark協議解析插件,wireshark能自動解析的協議都有一些共同的特點:

1. 協議數據包遵循嚴格的格式,每個字段代表的意義是固定的
2. 協議的各個字段的長度是規定好的,wireshark在解析的時候能夠"對號入座"地將數據包中的數據放入指定的字段變量中,並通過UI顯示出來

回到文章最開始分析的那個Chat.exe軟件使用的協議,它的情況是這樣的:

1. 使用位於TCP/IP之上的Socket進行通信
2. 發送的數據從本質上屬於"應用層數據"
3. 協議格式的長度並不是固定的,只是采用"松散""定界符"來標定協議格式

這種協議具有明顯的"格式松散型",我們無法采用TCP、IP、UDP那種嚴格格式的協議解析的方式來進行自動化協議解析,這個時候,wireshark的協議解析插件無法滿足我們的需求。我們針對這種已知數據格式、已知加密、壓縮算法的"應用層協議格式"可以直接根據wireshark(或者其他的嗅探軟件)抓包得到的.pcap數據包進行分析

0x1: pcap文件格式

1. 文件頭
    1) magic(4字節): pcap文件標識,目前為"D4 C3 B2 A1"
    2) major(2字節): 主版本號,#define PCAP_VERSION_MAJOR 2。即"02 00"
    3) minor(2字節): 次版本號,#define PCAP_VERSION_MINOR 4。即"04 00"
    4) thiszone(4字節): 時區修正,並未使用,目前全為0
    5) sigfigs(4字節): 精確時間戳,並未使用,目前全為0
    6) snaplen(4字節): 抓包最大長度,如果要抓全,設為0x0000ffff(65535),tcpdump -s 0就是設置這個參數,缺省為68字節
    7) linktype(4字節): 鏈路類型
        7.1) 0: BSD loopback devices, except for later OpenBSD
        7.2) 1: ethernet, and Linux loopback devices 
        7.3) 6: 802.5 Token Ring
        7.4) 7: ARCnet
        7.5) 8: SLIP
        7.6) 9: PPP
        7.7) 10: FDDI
        7.8) 100: LLC/SNAP-encapsulated ATM
        7.9) 101: "raw IP", with no link
        7.10) 102: BSD/OS SLIP
        7.11) 103: BSD/OS PPP
        7.12) 104: Cisco HDLC
        7.13) 105: 802.11
        7.14) 108: later OpenBSD loopback devices (with the AF_value in network byte order)
        7.15) 113: special Linux "cooked" capture
        7.16) 114: LocalTalk
2. 數據包頭
    1) ts(8字節): 抓包時間:
        1.1) 前4字節表示秒數
        1.2) 后4字節表示微秒數
    2) caplen(4字節): 保存下來的包長度(最多是snaplen,比如68字節),由此可以得到下一個數據幀的位置
    3) len(4字節): 數據包的真實長度,如果文件中保存的不是完整數據包,可能比caplen大
3. 數據包內容
按照TCP/IP協議族的規范逐層封裝數據,它的長度就是Caplen,這個長度的后面,就是當前PCAP文件中存放的下一個Packet數據包,也就 是說:PCAP文件里面並沒有規定捕獲的Packet數據包
之間有什么間隔字符串,下一組數據在文件中的起始位置。我們需要靠第一個Packet包確定

每個.pcap文件可能包含多個數據包,所以每個.pcap文件中有且只有一個"文件頭",1到多個"數據包頭+數據包"的組合

0x2: 編寫代碼對pcap數據包應用層自定義協議解析
梳理一下我們的目標:

1. 因為每個.pcap文件中一般會包含多個"數據包,"需要對.pcap中的多個"數據包"進行遍歷解析,使用數據包頭的"caplen"字段來進行分包
2. 一個.pcap中有很多數據包,我們只關心我們想要的數據包,特征如下:
    1) 目標協議產生的UDP通信數據包
    2) UDP數據包的內容的頭5個ASCII字符是[XIU<"
3. 過濾出目標協議產生的數據包之后,我們截取UDP數據包的"負載數據"(應用層數據),然后需要根據已知的解密算法進行解密、並根據目標協議的格式進行格式化UI顯示

code:

<html> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=utf8" /> 
<title>PCAP Parser</title></head> 
<table border="1"> 
<tr align="center" bgcolor="#66cc66"><td><b>Subject</b></td></tr>

<?php  

    $fpath = "./test.pcap";          //PCAP文件路徑   
    $TotalSize = filesize($fpath);   //計算PCAP文件大小,單位byte 
    //die(var_dump($totalSize));
    echo "<tr><td><b>" . $fpath . "</b> Size is: " . ($TotalSize / 1024) . " KB</td></tr>";  

    $f = fopen($fpath, "rb");        //打開PCAP文件  
    $FileHeader = fread($f, 24);     //讀取PCAP文件頭,長度 24 bytes. 

    //顯示文件頭信息
    echo "<tr bgcolor=\"#0066cc\" ><td colspan=2>Pcap Header Details</td></tr>"; 
    $linktype = array(
            0 => "BSD loopback devices, except for later OpenBSD",  
            1 => "ethernet, and Linux loopback devices",
            6 => "802.5 Token Ring",
            7 => "ARCnet",
            8 => "SLIP",
            9 => "PPP",
            10 => "FDDI",
            100 => "LLC/SNAP-encapsulated ATM",
            101 => "raw IP, with no link",
            102 => "BSD/OS SLIP",
            103 => "BSD/OS PPP",
            104 => "Cisco HDLC",
            105 => "802.11",
            108 => "later OpenBSD loopback devices (with the AF_value in network byte order)",
            113 => "special Linux cooked capture",
            114 => "LocalTalk"
        );
    $FileHeader = bin2hex($FileHeader); 
    echo "<tr><td>magic is: " . strtoupper(substr($FileHeader, 0, 8)) . "</td></tr>";  //magic
    echo "<tr><td>major(主版本號) is: " . (substr($FileHeader, 8, 2)) . "</td></tr>";  //major
    echo "<tr><td>minor(次版本號) is: " . (substr($FileHeader, 12, 2)) . "</td></tr>";  //minor
    echo "<tr><td>thiszone is: " . (substr($FileHeader, 16, 8)) . "</td></tr>";  //thiszone
    echo "<tr><td>sigfigs is: " . (substr($FileHeader, 24, 8)) . "</td></tr>";  //sigfigs
    $temp = str_split(substr($FileHeader, 32, 8), 2);   
    $temp = implode('', array_reverse($temp)); 
    $temp = hexdec($temp);
    echo "<tr><td>snaplen(wireshark設定的抓包最大長度配置信息) is: " . ($temp / 1024) . " KB</td></tr>";  //snaplen
    $temp = str_split(substr($FileHeader, 40, 8), 2);   
    $temp = implode('', array_reverse($temp)); 
    $temp = hexdec($temp);
    $temp = $linktype[$temp];
    echo "<tr><td>linktype(鏈路類型) is: " . $temp . "</td></tr>";  //linktype  
    //顯示頭信息 

    echo "<tr bgcolor=\"#ff0000\" ><td colspan=1></td></tr>";  
    $position = 24;         //$position文件指針位置,初始化為24,跳過文件頭,從24bytes后開始讀取第一個數據包  
    $n = 0;         //數據包序列,當前正在解析第幾個數據包  
    while($position < $TotalSize)       //從24bytes后開始讀取文件直至文件結束
    {  
        $n++;               //自增數據包序列 
        echo "<tr bgcolor=\"#0066cc\" ><td colspan=1>Pack $n </td></tr>";  
        fseek($f, $position);   //移動文件指針至24bytes的位置 
    
        //讀取16bytes的PCAP包頭信息(PackHeader)
        $PackHeader = fread($f, 16);   
        $PackHeader = bin2hex($PackHeader); //將PackHeader轉成16進制的字符串

        echo "<tr><td>PackHeader</td></tr>"; 
        //從PackHeader截取8個16進制的字符串(即2進制的4*8=3bits,4個字節的長度) 
        //Timestamp:時間戳高位,精確到seconds 
        $Sec = substr($PackHeader, 0, 8); 
        $Sec = substr($Sec, 6, 2).substr($Sec, 4, 2).substr($Sec, 2, 2).substr($Sec, 0, 2); 
        $Sec = hexdec($Sec); 
        //Timestamp:時間戳低位,精確到microseconds 
        $nSec = substr($PackHeader, 8, 8); 
        $nSec = substr($nSec, 6, 2).substr($nSec, 4, 2).substr($nSec, 2, 2).substr($nSec, 0, 2); 
        $nSec = hexdec($nSec); 
        //得出獲取這一數據幀的時間 
        $DateTime = date("Y-m-d H:i:s", $Sec); 
        echo "<tr><td>DateTime: ".$DateTime. "." .$nSec . "</td></tr>"; 

        //pack length 
        //Caplen:當前數據區的長度,即抓取到的數據幀長度,由此可以得到下一個數據幀的位置。 
        $Caplen = substr($PackHeader, 16, 8); 
        $Caplen = substr($Caplen, 6, 2).substr($Caplen, 4, 2).substr($Caplen, 2, 2).substr($Caplen, 0, 2); 
        $Caplen = hexdec($Caplen); 
        echo "<tr><td>Pack Caplen is: ".$Caplen." Byte</td></tr>";  

        //移動文件指針跳過PCAP包頭. 
        fseek($f, $position + 16); 
        //按PCAP包頭里的Caplen長度讀取PCAP包體內容.一個PCAP包體就相當於OSI中的數據鏈路層(Data Link)的一個幀. 
        //這里說"相當於"是因為在網絡上實際傳輸的數據包在數據鏈路層上每一個Packet開始都會有7個用於同步的字節(10101010, 10101010, 10101010, 10101010, 10101010, 
10101010, 10101010,)和一個用於標識該Packet開始的字節(10101011),最后還會有四個CRC校驗字節;而PCAP文件中會把前8個字節和最后4個校驗自己去掉,因為這些信息對於協議分
析是沒有用處的。
$PackData = fread($f, $Caplen); //取MAC地址,6個字節,48bits.轉成16進制表示,字符長度為12.即平時在系統看到的物理地址. //注意:這里PHP函數substr直接從$PackData的二進制內容里取內容,函數參數的單位為Byte.而不是字符串的字符個數了. $Dst = bin2hex(substr($PackData, 0, 6)); $Src = bin2hex(substr($PackData, 6, 6)); $Src = str_split($Src, 2); $Src = strtoupper(implode(':', $Src)); echo "<tr><td>Src MAC is: ".$Src."</td></tr>"; $Dst = str_split($Dst, 2); $Dst = strtoupper(implode(':', $Dst)); echo "<tr><td>Dst MAC is: ".$Dst."</td></tr>"; //取以太網類型,2個字節.並轉成16進制表示. $Ethertype = bin2hex(substr($PackData, 12, 2)); echo "<tr><td>Ethertype is: ".$Ethertype."</td></tr>"; //這里就開始進入OSI的網絡層(Network) if($Ethertype == "0800") { //IP包頭里4bits的版本和4bits的首部長度連續共占1個字節.取1個字節.並轉成16進制 $IPVersion_and_HeaderLen = bin2hex(substr($PackData, 14, 1)); //IP版本號4bits,就取一個16進制的字符,轉成10進制. $IPVersion = hexdec(substr($IPVersion_and_HeaderLen, 0, 1)); echo "<tr><td>IP Version is: ".$IPVersion."</td></tr>"; //IP首部長度4bits,也取一個16進制的字符,轉成10進制並乘以4.//這里乘以4是因為IP首部長度的單位是以32bits為一個單位,即4個byte為1個單位. //從這步得出的首部長度的單位為byte.基本上都是20 $IPHeaderLen = hexdec(substr($IPVersion_and_HeaderLen, 1, 1)) * 4; echo "<tr><td>IP Header Len is: ".$IPHeaderLen." Byte</td></tr>"; // 14-->32 // 總共跳過8個字節不做處理.(意義不是很大的字段). // 1字節的服務類型TOS(8bits), // 2字節的IP包總長度(16bits), // 2字節的標識(16bits) // 2字節的標志和片偏移(3bits標識,13bits片偏移) // 1字節的生存時間TTL(8bits) //第23字節開始取1個字節,8bits的協議類型.就是用來表示所搭載的上層協議類型的東西(如:TCP,UDP).轉成16進制表示. $Proctol = hexdec(bin2hex(substr($PackData, 23, 1))); //取4個字節的源IP地址.並轉成常見的4段表示格式. $SrcIP = bin2hex(substr($PackData, 26, 4)); $SrcIP = hexdec($SrcIP[0].$SrcIP[1])."." .hexdec($SrcIP[2].$SrcIP[3])."." .hexdec($SrcIP[4].$SrcIP[5])."." .hexdec($SrcIP[6].$SrcIP[7]); //取4個字節的目標IP地址.並轉成常見的4段表示格式. $DecIP = bin2hex(substr($PackData, 30, 4)); $DecIP = hexdec($DecIP[0].$DecIP[1])."." .hexdec($DecIP[2].$DecIP[3])."." .hexdec($DecIP[4].$DecIP[5])."." .hexdec($DecIP[6].$DecIP[7]); echo "<tr><td>SrcIP Addr is: ".$SrcIP."</td></tr>"; echo "<tr><td>DecIP Addr is: ".$DecIP."</td></tr>"; // 據協議類型開始處理OSI的傳輸層的數據. // 一般的協議類型:6 TCP, 17 UDP, 1 ICMP //在這里只處理UDP類型的協議. if ($Proctol == "17") { echo "<tr><td>Data Proctol is: ".$Proctol."</td></tr>"; $SrcPort = hexdec(bin2hex(substr($PackData, 34, 2))); $DecPort = hexdec(bin2hex(substr($PackData, 36, 2))); echo "<tr><td>Src Port is: ".$SrcPort."</td></tr>"; echo "<tr><td>Dec Port is: ".$DecPort."</td></tr>"; $Data = substr($PackData, 42); //對加密數據進行解密 $seed = array(6, 5, 4, 3, 2, 1, 0); $len = 0; $i = 0; $len = strlen($Data); for($i = 0; $i < $len; $i++) { $Data[$i] = chr(ord($Data[$i]) - $seed[$i % 7]); } echo "<tr bgcolor=\"#ffcc66\"><td>Msg Data: $Data</textarea></td></tr>"; } else { //echo "<tr bgcolor=red><td>WARNING: This is not an UDP Data, I will jump to NEXT.</td></tr>"; } } else { //echo "<tr bgcolor=red><td>WARNING: This is not an IP PackData, I will jump to NEXT.</td></tr>"; } $position = $position + 16 + $Caplen; } fclose($f); ?> </table>

完成PCAP包的解析

 

Copyright (c) 2014 LittleHann All rights reserved

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM