Wireshark的Pcap文件格式分析及解析源码【转】

本文介绍PCAP文件格式,包括文件头、数据包头及数据包结构,并提供了一个针对自定义UDP抓包文件的具体解析实现。

pcap文件格式是常用的数据报存储格式,包括wireshark在内的主流抓包软件都可以生成这种格式的数据包
下面对这种格式的文件简单分析一下: 
 
pcap文件的格式为:
  文件头    24字节
  数据报头 + 数据报  数据包头为16字节,后面紧跟数据报
  数据报头 + 数据报  ......
pcap.h里定义了文件头的格式
struct pcap_file_header {
        bpf_u_int32 magic;
        u_short version_major;
        u_short version_minor;
        bpf_int32 thiszone;    
        bpf_u_int32 sigfigs;   
        bpf_u_int32 snaplen;   
        bpf_u_int32 linktype;  
};

Pcap文件头24B各字段说明:

Magic:4B:0×1A 2B 3C 4D:用来识别文件自己和字节顺序。0xa1b2c3d4用来表示按照原来的顺序读取,0xd4c3b2a1表示下面的字节都要交换顺序读取。一般,我们使用0xa1b2c3d4
Major:2B,0×02 00:当前文件主要的版本号
Minor:2B,0×04 00当前文件次要的版本号
ThisZone:4B 时区。GMT和本地时间的相差,用秒来表示。如果本地的时区是GMT,那么这个值就设置为0.这个值一般也设置为0 SigFigs:4B时间戳的精度;全零
SnapLen:4B最大的存储长度(该值设置所抓获的数据包的最大长度,如果所有数据包都要抓获,将该值设置为65535; 例如:想获取数据包的前64字节,可将该值设置为64)
LinkType:4B链路类型
常用类型:
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

Packet 包头和Packet数据组成
字段说明:
Timestamp:时间戳高位,精确到seconds(值是自从January 1, 1970 00:00:00 GMT以来的秒数来记)
Timestamp:时间戳低位,精确到microseconds (数据包被捕获时候的微秒(microseconds)数,是自ts-sec的偏移量)
Caplen:当前数据区的长度,即抓取到的数据帧长度,由此可以得到下一个数据帧的位置。
Len:离线数据长度网络中实际数据帧的长度,一般不大于caplen,多数情况下和Caplen数值相等。
(例如,实际上有一个包长度是1500 bytes(Len=1500),但是因为在Global Header的snaplen=1300有限制,所以只能抓取这个包的前1300个字节,这个时候,Caplen = 1300 )
Packet 数据:即 Packet(通常就是链路层的数据帧)具体内容,长度就是Caplen,这个长度的后面,就是当前PCAP文件中存放的下一个Packet数据包,也就 是说:PCAP文件里面并没有规定捕获的Packet数据包之间有什么间隔字符串,下一组数据在文件中的起始位置。我们需要靠第一个Packet包确定。 最后,Packet数据部分的格式其实就是标准的网路协议格式了可以任何网络教材上找得到。




以下是我的实现,针对自定义的UDP的抓包文件进行解析
typedef struct tagIpHead
{
    int version;//版本
    int headLength; //头长度
    int  diffsever; 
    int  totallength; //总长度
    int  identification; 
    int  flag;
    int  fragment;
    int  ttl;
    int  protocoltype; //协议类型
    int  checksum;
    unsigned long  srcip;//源ip
    unsigned long  dstip;//目的ip
}IP_HEAD;
typedef struct tagUdpHead

    unsigned short  srcport; //源端口
    unsigned short  dstport; //目的端口
    int   length; //udp包长度
}UDP_HEAD;

unsigned long FileParse::Parse( const char* FileName,bool& bThreadRun)//,HWND hwnd )
{
    if (_wass_session)
    {
        delete _wass_session;
        _wass_session = NULL;
    }
    _wass_session = new WassSessions();
    //////////////////////////////////////////////////////////////////////////
    unsigned long lRes =0;
    FILE* pFile=NULL;
    int nReadSize = 0;
    char buff[FILE_READ_LEN];
    char acip[30];
    char portalip[30];
    char radiusip[30];
    unsigned long timestamp1;
    unsigned long timestamp2;
    CConfigure* config=new CConfigure();

    if (config)
    {
        //读取ip地址,添加到iplist中
        unsigned long ipTmp=0; 
        unsigned short portTmp=0;
        config->getIPConfig(acip,portalip,radiusip);
        cut_ip(acip,ipTmp,portTmp);
        acport_list.push_back(portTmp);
        acip_list.push_back(ipTmp);
        cut_ip(portalip,ipTmp,portTmp);
        portalip_list.push_back(ipTmp);
        portalport_list.push_back(portTmp);
        delete config;
        config = NULL;
    }
    //////////////////////////////////////////////////////////////////////////
    memset(buff,0,FILE_READ_LEN);
    do 
    {


        pFile =fopen(FileName,"rb"); 
        //pFile =_open( FileName, _O_RDONLY | _O_BINARY ); 
        if (!pFile)
        {
            //failed for the file opened
            fprintf(stderr, "Open the file failed:%s ", strerror(errno));

            lRes = 2;
            break;
        }
        nReadSize = fread(buff,sizeof(char),24,pFile);
        if (nReadSize == 24)
        {
            while (!feof(pFile) && bThreadRun) 
            {
                memset(buff,0,FILE_READ_LEN);
                nReadSize = fread(buff,sizeof(char),16,pFile);
                unsigned long nPacketLen=0;
                memcpy(&timestamp1,buff,4);
                memcpy(&timestamp2,buff+4,4);
                memcpy(&nPacketLen,buff+8,4);
                //nPacketLen = ntohl(nPacketLen);
                char* buf = new char[nPacketLen];
                memset(buf,0,nPacketLen);

                int nReadCount=0;
                //读取包
                while (nReadCount < nPacketLen)
                {
                    nReadSize = fread(buff,sizeof(char),nPacketLen-nReadCount,pFile);
                    memcpy(buf+nReadCount,buff,nReadSize);
                    nReadCount += nReadSize;
                }
                //在此处处理ip/udp包部分
                int nOffset=14;//数据偏移位置
                _ip->Parse(buf+nOffset);//ip解析
                if(_ip->wass_ip_head.protocoltype==17)//只处理UDP
                {
                    nOffset += 20;
                    _udp->Parse(buf+nOffset);//udp解析
                    nOffset +=8;
                    std::list<unsigned long>::iterator acit= acip_list.begin();
                    std::list<unsigned long>::iterator portalit = portalip_list.begin();
                    bool bFoundIP = false;
                    //暂时不考虑算法,遍历ip地址
                    //while (acit++ != acip_list.end())
                    for (;acit != acip_list.end();acit++)
                    {
                        unsigned long aIP = *acit;
                        char aTmp[20];

                        IPULongToString(aIP,aTmp);
                        IPULongToString(_ip->wass_ip_head.dstip,aTmp);
                        if (_ip->wass_ip_head.dstip== *acit  || _ip->wass_ip_head.srcip == *acit)
                        {
                        for (;portalit !=portalip_list.end();portalit++)
                        {
                            if (_ip->wass_ip_head.dstip== *portalit  || _ip->wass_ip_head.srcip == *portalit)
                            {
                                bFoundIP = true;
                                break;
                            }
                        }
                        break;
                        }
                    }
                    if (bFoundIP)
                    {
                        //此处是表示可以进行数据的解析
                        _portalPacket = new CPortalPacket();
                        _portalPacket->parse(buf + nOffset,nPacketLen - nOffset);
                    //设置包的源IP和目的IP,源端口,目的端口
                        _portalPacket->setIpAndPort(_ip->wass_ip_head.srcip,
                            _ip->wass_ip_head.dstip,_udp->wass_udp_head.srcport,_udp->wass_udp_head.dstport);
                        _portalPacket->setPacketTime(timestamp1,timestamp2); 
                        _wass_session->AddPacket(_portalPacket,_sessions);
                    }
                    else
                    {
                        
                    }
                }
                if (buf)
                {
                    delete [] buf;

                }

            }
        }
    } while (false);
    if (pFile)
    {
        fclose(pFile);
    }

    //////////////////////////////////////////////////////////////////////////
    //::PostMessage(_hwnd,WM_FINISHED,0,0);
    return lRes;
}

wireshark源码分析问题这几天在看wireshark(ethereal)源代码。看源代码的主要兴趣点是它的分析模块(dissect)。分析之后他的数据存在哪儿,怎么打印的(-V参数)。我想把分析后的数据,提取出来,存在自己定义的数据结构里面,或者按我自己的格式写入文本中。 看了几天,对一些数据结构,似懂非懂,一些流程也是似懂非懂。可能由于经验不足的原因,搞来搞去就在几个函数,结构体里面打。好几次以为找到切入点,发现又回来原来的起点。 这两天看晕了。有点打击,水平太差劲了。。呵呵。先这边问问,看看有没有熟悉的朋友。指点一下。先谢谢了。 这样问问题可能太细了。感觉也不大合适。 1. 我应该如何来看代码?如何找到突破点? 2. 有wireshark有了解的朋友,说说你们关于源码剖析的体会。 3. 说什么都可以,朋友觉得对我有用,有启发就好。千万别 “我顶,UP啊”。呵呵:emn23:我觉得重要的是看 pcap库 本帖最后由 peidright 于 2010-04-02 16:36 编辑 楼上说得对!。 看源代码之前,问下你自己,看代码的目的是什么? 对于 wireshark 来说,你是想学他写界面? 还是抓包? 还是业务逻辑? 界面的话,wireshark 还行 抓包的话,应该看pcap库 业务逻辑的话。不应该看wireshark,看tcpdump.看下啊,:em03:看看这个也许对你有帮助 添加一个基础的RDP解析器 下面我们将循序渐进地设计一个基础的RDP解析器。它依次包含如下构成要素: 包类型字段(占用8比特位,可能的值为:1,初始;2,终结;3,数据); 标志集字段(占用8比特位:0x01,开始包;0x02,结束包;0x04先包); 序列号字段(占用16比特位); 1. 创建解析器 首先您需要选择解析器的类型:内置型(包含在主程序中)或插件型。 插件是容易编写的,先做一个插件型解析器吧。 例1. 解析器初始设定. #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include void proto_register_rdp(); void proto_reg_handoff_rdp(); static void dissect_rdp(tvbuff_t *tvb,packet_info *pinfo,proto_tree *tree); static int proto_rdp=-1; static dissector_handle_t rdp_handle; static gint ett_rdp = -1; define TCP_PORT_RDP 3389 void proto_register_rdp(void) { proto_rdp=proto_register_protocol( "RDP Protocol", "RDP", "rdp"); } 现在来逐一分析这段代码。首先我们有一些常规的包含文件,最好依惯例在文件开始包含进来。随后是一些函数的前置声明,我们稍后定义它们。 接下来我们定义了一个整型变量"proto_rdp"用于记录我们的协议注册信息。它被初始化为"-1",当解析器注册到主程序中后,其值便会得到更新。这样做可保证我们方便地判断是否已经做了初始工作。将所有不打算对外输出的全局变量和函数声明为"static"是一个良好的习惯,因为这可以保证命名空间不被污染。通常这是容易做到的,除非您的解析器非常庞大以致跨越多个文件。 之后的模块变量"TCP_PORT_RDP"则包含了协议使用的TCP端口号,我们会对通过该端口的数据流进行解析。 solaris10下proc编译问题 >紧随其后的是解析器句柄"rdp_handle",我们稍后对它进行初始化。 至此我们已经拥有了和主程序交互的基本元素,接下来最好再把那些预声明的函数定义一下,就从注册函数"proto_register_rdp"开始吧。 首先调用函数"proto_register_protocol"注册协议。我们能够给协议起3个名字以适用不同的地方。全名和短名用在诸如"首选项(Preferences)"和"已激活协议(Enabled protocols)"对话框以及记录中已生成的域名列表内。缩略名则用于过滤器。 下面我们需要一个切换函数。 例2. 解析器切换. void proto_reg_handoff_rdp(void) { static gboolean initialized=FALSE; if(!initialized) { rdp_handle = create_dissector_handle(dissect_rdp, proto_rdp); dissector_add("tcp.port", TCP_PORT_RDP, rdp_handle); initialized=TRUE; } } 这段代码做了什么呢?如果解析器尚未初始化,则对它进行初始化。首先创建解析器。这时注册了了函数"dissect_rdp"用于完成实际的解析工作。之后将该解析器与TCP端口号相关联,以使主程序收到该端口的UDP数据流时通知该解析器。 至此我们终于可以写一些解析代码了。不过目前我们仅写点儿基本功能占个位置。 例3.解析 static void dissect_rdp(tvbuff_t *tvb,packet_info *pinfo,proto_tree *tree) { if(check_col(pinfo->cinfo, COL_PROTOCOL)) { col_set_str(pinfo->cinfo, COL_PROTOCOL, "RDP"); } if(check_col(pinfo->cinfo,COL_INFO)) { col_clear(pinfo->cinfo,COL_INFO); } } 该函数用于解析传递给它的数据包。包数据由"tvb"参数指向的特殊缓冲区保管。现在我们已深入到协议的细节,对它们您肯定是了若指掌。包信息结构参数"pinfo"包含了协议的基本数据,以供我们更新。参数"tree"则指明了详细解析发生的地方。 这里我们仅做了保证通过的少量工作。前两行检查UI中"协议(Protocol)"列是否已显示。如果该列已存在,就在这儿显示我们的协议名称。这样人们就知道它被识别出来了。另外,如果"信息(INFO)"列已显示,我们就将它的内容清除。 至此我们已经准备好一个可以编译和安装的基本解析器。不过它目前只能识别和标示协议。 为了编译解析器并创建插件,还需要在解析器代码文件"packet-rdp.c"所在目录下创建一些提供支持的文件: - Makefile.am - UNIX/Linux的makefile模板 - Makefile.common - 包含了插件文件的名称 - Makefile.nmake - 包含了针对Windows平台的Wireshark插件makefile - moduleinfo.h - 包含了插件版本信息 - moduleinfo.nmake - 包含了针对Windows平台的DLL版本信息 - packet-rdp.c - 这是您的解析器原代码文件 - plugin.rc.in - 包含了针对Windows平台的DLL资源模板 "Makefile.common"和"Makefile.am"文件中涉及到相关文件解析器名称的地方一定要修改正确。"moduldeinfo.h"和"moduleinfo.nmake"文件中的版本信息也需要正确填充。一切准备妥善后就可以将解析器编译为DLL或共享库文件了(使用nmake工具)。在wireshark文件夹下的"plugins"文件夹中,建立"rdp"文件夹。将修改过的Makefile.common,Makefile.am,moduleinfo.nmake,moduldeinfo.h,Makefile.nmake及packet-rdp.c文件考到"rdp"文件夹下,然后进行编译,rdp插件自动生成完整,就可以正常工作了。 1. 解析协议细节 现在我们已经有了一个可以运用的简单解析器,让我们再为它添点儿什么吧。首先想到的应该就是标示数据包的有效信息了。解析器在这方面给我们提供了支持。 首先要做的事情是创建一个子树以容纳我们的解析结果。这会使协议的细节显示得井井有。现在解析器在两种情况下被调用http://www.boomss.com:其一,用于获得数据包的概要信息;其二,用于获得数据包的详细信息。这两种情况可以通过树指针参数"tree"来进行区分。如果树指针为NULL,我们只需要提供概要信息;反之,我们就需要拆解协议完成细节的显示了。基于此,让我们来增强这个解析器吧。 例4 static void dissect_rdp(tvbuff_t *tvb,packet_info *pinfo,proto_tree *tree) { proto_item *ti=NULLV; if(check_col(pinfo->cinfo,COL_PROTOCOL)) { col_set_str(pinfo->cinfo,COL_PROTOCOL,"RDP"); } if(check_col(pinfo->cinfo,COL_INFO)) { col_clear(pinfo->cinfo,COL_INFO); } if(tree) { ti = proto_tree_add_item(tree, proto_rdp, tvb, offset, -1, FALSE);} } 这里我们为解析添加一个子树。它将用于保管协议的细节,仅在必要时显示这些内容。 我们还要标识被协议占据的数据区域。在我们的这种情况下,协议占据了传入数据的全部,因为我们假设协议没有封装其它内容。因此,我们用"proto_tree_add_item"函数添加新的树结点,将它添加到传入的协议树"tree"中,用协议句柄"proto_rdp"标识它,用传入的缓冲区"tvb"作为数据,并将有效数据范围的起点设为"0",长度设为"-1"(表示缓冲区内的全部数据)。至于最后的参数"FALSE",我们暂且忽略。 做了这个更改之后,在包明细面板区中应该会出现一个针对该协议的标签;选择该标签后,在包字节面板区中包的剩余内容就会高亮显示。 现在进入下一步,添加一些协议解析功能。在这一步我们需要构建一组帮助解析的表结构。这需要对"proto_register_rdp"函数做些修改。首先定义一组静态数组。 例5 定义数据结构 static hf_register_info hf[]= { { &hf;_rdp_version, { "TPKT Header:Version", "rdp.version",
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值