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

 

 

posted @ 2014-05-31 23:37  郑瀚Andrew  阅读(8852)  评论(5编辑  收藏  举报