精华内容
下载资源
问答
  • 长话短说,wireshark有一个follow tcp stream功能,这个功能很方便。美中不足的是提取出的stream 数据没有时间戳等其他信息,在分析数据...原理很简单,仍然是基于wireshark,里面有一个Export packet dissection as...

    长话短说,wireshark有一个follow tcp stream功能,这个功能很方便。美中不足的是提取出的stream 数据没有时间戳等其他信息,在分析数据的延时和丢包问题时就有些力不从心了。这里简单用python实现了一个简单follow tcp stream功能,同时保留了tcp信息。

    原理很简单,仍然是基于wireshark,里面有一个Export packet dissection as XML ‘pdml’ file。 导出来之后的文件内容是这个样子的:

    看到上面的内容,我想什么都不用说了。用python简单的做个xml文件解析,将数据提取出来就可以了。

    那么剩下的一个问题就是follow tcp stream 这个算法如何实现?本质就是一个tcp数据如何重组的过程,具体可以参考这篇博文TCP数据包重组实现分析

    这里,简单起见,我做了一些约束:

    只能提取A-->B这样的单个方向的数据。如果需要提取B-->A的数据,可以重新过滤一下数据,然后执行一次脚本。忽略最开头的syn包和断开连接时的Fin包。 基于上面两个简化,实际算法可以简化为根据tcp帧中的seq,从小到大排序。简单举个例子:有三个tcp包,按seq排序后如下样子

    (seq=1, nxtseq=5, data='1234') , (seq=4, nxt=6, data='45') , (seq=7,nxt=8, data='7')

    第一个数据包的nxtseq > 第二个数据的seq,说明两个数据包之间有数据重复,事实也是如此,重复了数字‘4’

    第二个数据包的nxtseq < 第三个数据包的seq,说明这两个数据包之间有丢帧。事实也是如此,丢失了数字‘6’

    好了,原理就介绍到这儿。

    剩下稍微介绍一下wireshark过滤规则和这个算法的局限性

    按照ip过滤某个方向的数据,一般可以使先执行wireshark的follow tcp stream功能,一般在filter一栏会有这样一个表达式 tcp.stream eq xxx。在这个表达式的后面可以继续跟上ip过滤的表达式: tcp.steam eq xxx and ip.src==xxx and ip.dst==xxx按照tcp端口号过滤某个方向的数据。首先同ip过滤,先固定到一条tcp连接上,得到tcp.stream eq xxx。 然后加上端口过滤:tcp.stream eq xxx and tcp.srcport==xxx and tcp.dstport==xxx这个工具的局限性,因为是基于python element tree 对xml文件进行解析和数据提取,所以即使是解析100M的pcap文件,首先生成的pdml文件就会暴增到几百兆,然后这几百兆的文件又要被读入内存,(python element tree的特点),总计下来就是生成pdml文件有点慢(几分钟),内存消耗特大,几百兆。

    最后简单贴一些关键代码。完整的脚本可以从这里免费下载 TCPParser -- follow tcp stream by python

    这是从pdml文件的某个proto中提取需要的元素信息。

    def extract_element(self, proto, elem):

    result = dict()

    for key in elem.keys():

    result[key]=

    fieldname =

    attribname =

    for field in proto.findall(field):

    fieldname = field.get('name')

    if fieldname in elem:

    attribname = elem[fieldname]

    result[fieldname] = field.get(attribname, '')

    return result

    def regularize_stream(self, frame_list):

    '''

    正则化tcp stream的数据,主要是根据seq,nxtseq补上缺少的segment,以及删除重复的数据

    不少缺少的segment时,data为空,frame.number='lost'

    删除重复的数据时,尽可能保留较早之前收到的数据,也就是前一个包的数据

    '''

    self.reporter.title(TCPParser regularize timestamp)

    timer = Timer(regularize_stream_data).start()

    reg_frame_list = []

    expectseq = -1

    first = True

    for frame in frame_list:

    if first:

    # 第一个数据包

    first = False

    expectseq = frame[tcp.nxtseq]

    reg_frame_list.append(frame)

    continue

    # 从第二个数据包开始

    seq = frame[tcp.seq]

    nxtseq = frame[tcp.nxtseq]

    if seq == expectseq :

    # 数据刚好,完全连续,不多不少

    if nxtseq == 0: continue # 表示ack包,无意义

    expectseq = nxtseq

    reg_frame_list.append(frame)

    elif seq > expectseq:

    # 数据有缺失,说明丢包了

    self.reporter.error(previous tcp segment is lost: + str(frame[TCPFrame.KEY_FRAMENo]))

    # newpacket = self.new_lost_packet(frame, str(expectseq), str(seq))

    # reg_frame_list.append(newpacket)

    reg_frame_list.append(frame)

    expectseq = nxtseq

    elif seq < expectseq:

    # 数据有重叠,数据重传时补传过多数据了

    self.reporter.warning(tcp segment retransmission: + str(frame[TCPFrame.KEY_FRAMENo]))

    if expectseq < nxtseq:

    # 当前数据包需要舍弃一部分内容

    # pre_packet[-(expectseq-seq):-1] == frame[0:expectseq-seq]

    frame[tcp.seq] = expectseq

    frame[data] = frame[data][expectseq-nxtseq:]

    frame[datalen] = len(frame[data])

    expectseq = nxtseq

    reg_frame_list.append(frame)

    else:

    # 当前数据包的内容可以完全舍弃

    # expectseq 保持不变

    # pre_packet[-(nxtseq-seq):] = frame[:nextseq-seq]

    pass

    timer.stop()

    return reg_frame_list

    展开全文
  • 网络原理 1、那种网络可以抓住数据包 本机环境 : 直接抓包 通过观察本机的网卡检测流量的进出(默认的 软件会绑定一个网卡用于检测进出的流量) 集线器环境: 因为 集线器是一种物理层的设备 他不会识别数据包 只...

    网络原理

    1、那种网络可以抓住数据包
    本机环境 :
    直接抓包 通过观察本机的网卡检测流量的进出(默认的 软件会绑定一个网卡用于检测进出的流量)
    本机环境抓包示意图
    集线器环境:
    因为 集线器是一种物理层的设备 他不会识别数据包 只会将接受到的数据包进行防洪法发送出去 抓包软件可以可以通过 网络监视数据包 进行抓包分析
    在这里插入图片描述
    交换机环境
    (1)端口镜像方式
    交换机的转发数据的模式 是根据它的交换表进行的 一般的情况下是不会被抓取到数据包的 所以要是实现成功的抓取数据包需要获得交换机的控制权限,通过 在交换机上设置 端口镜像的方式,实现将经过该交换机的流量拷贝一份,复制转发到抓包主机网卡上 进行数据分析。
    在这里插入图片描述

    (2)ARP的方式、
    1、假如PC2要与PC3实现通信 会事先发送一个广播ARP请求 通过交换机 发送给主机PC3 和PC1 但是PC1会丢弃不处理在这个信息
    2使用 ARP攻击软件后 拍出1 主机会接受 这个数据包并且返回数据请求 向pc2解释我才是这个主机 而且会发送不止一个请求 根据 ARP 后到优先的规则 混乱 交换机的MAC地址表从而实现 成功接收数据
    在这里插入图片描述
    3、MAC防洪
    PC1 大量发送MAC地址信息 到交换机上 是的交换机的MAC地址表混乱 从而是的主机发送的数据包无法被识别 从而 使用防洪的方式 发送给所有主机

    在这里插入图片描述

    底层原理

    抓包的底层架构
    在这里插入图片描述

    win-/libpcap 是wireshark抓包时候依赖的库文件 也就是底层驱动
    capture 抓包引擎 利用 libpcap/winpacp从底层抓取数据包,libpcap/winpacp提供了通用的抓包端口(包括以太网,令牌环网,ATM网等)获取数据包
    wiretap 包格式支持引擎 从抓取的数据包中 读取解析数据包 支持多种文件格式
    core 核心引擎 用于通过各种的函数引用 各种的插件 共同的调度 实现抓包的 过程
    GTK1/2 图形化处理工具 处理用户的输入输出显示

    展开全文
  • 工作原理 每个解析器解码自己的协议部分, 然后把封装协议的解码传递给后续协议。 因此它可能总是从一个Frame解析器开始, Frame解析器解析捕获文件自己的数据包细节(如:时间戳), 将数据交给一个解码Ethernet头部的...

    工作原理

    每个解析器解码自己的协议部分, 然后把封装协议的解码传递给后续协议。

    因此它可能总是从一个Frame解析器开始, Frame解析器解析捕获文件自己的数据包细节(如:时间戳), 将数据交给一个解码Ethernet头部的Ethernet frame解析器, 然后将载荷交给下一个解析器(如:IP), 如此等等. 在每一步, 数据包的细节会被解码并显示.

    可以用两种可能的方式实现协议解析. 一是写一个解析器模块, 编译到主程序中, 这意味着它将永远是可用的. 另一种方式是实现一个插件(共享库/DLL), 它注册自身用于处理解析。

    插件形式和内置形式的解析器之间的差别很小. 在Windows平台, 通过列于libwireshark.def中的函数, 我们可以访问有限的函数, 但它们几乎已经够用了.

    比较大的好处是插件解析器的构建周期要远小于内置. 因此以插件开始会使最初的开发工作变得简单, 而最终代码的布署会和内置解析器一样。

    另见 README.developer  文件doc/README.developer包含更多有关实现解析器(而且在某些情况下, 比本文档要新一些)的信息.

    添加基本解析器

    让我们一步一步来实现一个杜撰的“foo”协议的基本解析器。此协议包含以下基本项:

    • packet type 8 bits, 可能值:1-初始化,2-终止,3-数据
    • flags 8 bit, 0x01-开始packet, 0x02-结束packet, 0x04-优先packet
    • seq number 16 bits
    • 1个ip地址

    构建解析器

    首先需要决定解析器是要以built-in方式,还是以plugin方式实现。plugin方式实现比较容易上手。

    代码9.1 解析器初始化

    #include "config.h"
    #include <epan/packet.h>
    
    #define FOO_PORT 9877
    
    static int proto_foo = -1;
    
    
    void
    proto_register_foo(void)
    {
        proto_foo = proto_register_protocol (
            "FOO Protocol", /* name       */
            "FOO",      /* short name */
            "foo"       /* abbrev     */
            );
    }
    

    首先include一些必需的头文件。proto_foo用来记录我们的协议,当将此解析器注册到主程序时,它的值将会更新。把所有非外部使用的变量和函数声明为static是一个好的编程实践,可以避免名字空间污染。一般情况下这不是问题,除非我们的解析器非常大,分成了多个文件。

    我们#define了协议的UDP端口FOO_PORT。

    现在我们已经有了与主程序交互所需的基本东西了。接下来实现2个解析器构建函数(dissector setup functions)。

    首先调用proto_register_protocol()函数来注册协议。可以给它3个名字用来将来在不同的地方显示。比如full和short name用于“Preferences”和“Enabled protocols”对话框。abbrev name用于显示过滤器。

    接下来我们需要handoff例程。

    代码9.2 解析器handoff

    void
    proto_reg_handoff_foo(void)
    {
        static dissector_handle_t foo_handle;
    
        foo_handle = create_dissector_handle(dissect_foo, proto_foo);
        dissector_add_uint("udp.port", FOO_PORT, foo_handle);
    }
    

    首先创建一个dissector handle,它和foo协议及执行实际解析工作的函数关联。接下来将此handle与UDP端口号关联,以便主程序在看到此端口上的UDP数据时调用我们的解析器。

    标准wireshark解析器习惯是把proto_register_foo()和proto_reg_handoff_foo()做为解析器代码的最后2个函数。

    最后我们来编写一些解析器代码。目前将它做为基本的占位符。

    代码9.3 协议解析

    static void
    dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        /* Clear out stuff in the info column */
        col_clear(pinfo->cinfo,COL_INFO);
    }
    

    此函数用于解析交给它的packets。packet数据放在名为tvb的特殊缓存中。对此随着我们对协议细节了解的深入将会变得非常熟悉。packet_info结构包含有关协议的一般数据,我们应该在此更新信息。tree参数是细节解析发生的地方。

    现在我们进行最小化的实现。第1行我们设置我们协议的文本,以示用户可以看到协议被识别了。另外唯一做的事情是清除INFO列中的所有数据,如果它正在被显示的话。

    此时,我们已经准备好基本的解析器,可以进行编译和安装了。它什么也不做,除了识别协议并标识它。

    为了编译此解析器并创建插件,除了packet-foo.c中的源代码,还有一堆必需的支持文件,它们是:

    • Makefile.am - This is the UNIX/Linux makefile template
    • Makefile.common - This contains the file names of this plugin
    • Makefile.nmake - This contains the Wireshark plugin makefile for Windows
    • moduleinfo.h - This contains plugin version info
    • moduleinfo.nmake - This contains DLL version info for Windows
    • packet-foo.c - This is your dissector source
    • plugin.rc.in - This contains the DLL resource template for Windows

    你可以在plugin目录内找到这些文件的好例子。Makefile.common和Makefile.am必须被修改,以反映相关的文件和解析器名字。moduleinfo.h和moduleinfo.nmake必须被填充版本信息。将解析器编译为DLL或共享库,然后将它拷贝到wireshark的plugin目录。

    以下内容参考wireshark-1.8.4\doc\README.plugins


     以foo协议解析器插件为例,构建此插件至少需要在wireshark-1.8.4/plugins/foo目录下建立以下文件:

    • AUTHORS
    • COPYING
    • ChangeLog
    • CMakeLists.txt
    • Makefile.am
    • Makefile.common
    • Makefile.nmake
    • moduleinfo.h
    • moduleinfo.nmake
    • plugin.rc.in
    • 当然,还有自己编写的解析器源代码

    这些文件的例子可以在plugins/gryphon中找到。

    AUTHORS,COPYING,ChangeLog:GPL工程的标准文件。

    CMakeLists.txt:把gryphon/CMakeLists.txt文件中出现的所有"gryphon"替换为"foo",并把自己的源文件添加到DISSECTOR_SRC变量。

    Makefile.am:把gryphon/Makefile.am文件中出现的所有"gryphon"替换为"foo"。

    Makefile.common:此文件只应列出export register_*()和handoff_*()的main源文件。所有其他支持源文件应该列在DISSECTOR_SUPPORT_SRC变量中。如果解析器有头文件,必须列在DISSECTOR_INCLUDES变量中。DISSECOTR_INCLUDES变量不应包括moduleinfo.h。

    Makefile.nmake:不需要改动。

    moduleinfo.h:用于设置此插件的版本信息。

    moduleinfo.nmake:用于设置编译此插件的版本信息。它的内容应与moduleinfo.h匹配。

    plugin.rc.in:这是Windows资源模板文件,用于把插件的特定信息做为资源添加到DLL。不需要修改。


    把以上文件准备好、修改好之后,cmd进入plugins/foo目录,运行nmake -f Makefile.nmake xxx来进行编译,就像编译wireshark源码一样。编译好之后生成foo.dll,将它拷贝到编译好的wireshark的plugins目录(可能会有中间目录,视情况)。

    可以编写简单的udp发送端和接收端来测试刚才编译的foo协议解析插件。

    发送端

    #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 = inet_addr("192.168.1.2");
    
        char buff[] = "hello world";
        puts(buff);
    
        for(;;)
        {
            sendto(sockfd, buff, (int)strlen(buff), 0, (SOCKADDR*)&addr, sizeof(addr));
            Sleep(1000);
        }
    
        closesocket(sockfd);
    
        WSACleanup();
    
        return 0;
    }
    

    接收端

    #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;
    }
    

    然后可以运行这2个程序,开启添加了foo协议解析插件(所谓添加也就是把foo.dll复制到plugins目录)的wireshark进行监测。

    由于这2个程序在一台机器上运行,使用了环回接口,而wireshark不会捕获环回接口,因此需要配置一下路由:

    # windows7之类的系统需要使用管理员权限来执行# 192.168.1.2是本机IP,192.168.1.1是路由器IP
    C:\Windows\system32>route add 192.168.1.2 mask 255.255.255.255192.168.1.1 metric 1

    运行后结果:

    解析协议的细节

    接下来可以做一些复杂一点的解析工作。最简单的事情是对载荷进行标记。

    首先创建一个subtree用来放解析结果。这有助于在detailed display中更佳显示。对解析器的调用有2种情况。一种情况用于获取packet的摘要,另一种情况用于解析packet的细节。这两种情况由tree指针的不同来区别。如果tree指针为NULL,用于获取简略信息。如果是非NULL,则需要解析协议的各个细部。记住这些后,让我们来增强我们的解析器。

    代码9.4 plugin packet解析

    static void
    dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
    
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        /* Clear out stuff in the info column */
        col_clear(pinfo->cinfo,COL_INFO);
    
        if (tree) { /* we are being asked for details */
            proto_item *ti = NULL;
            ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
        }
    }
    

    这里所做的是把一个subtree加入到解析中。此subtree会保存此协议的所有细节,且不会在不需要时弄乱显示。

    我们还可以标记被此协议所消费的数据区域。在目前的情况下,这统治是传递过来的所有数据,因为我们假定此协议不再封装其他协议。因此,我们用proto_tree_add_item()往tree里添加新的节点,标识它的协议名,用tvb缓冲区做为数据,并消费此数据的0到最后1个字节(-1表示结束)。ENC_NA(not applicable)是编码参数。

    在这些改变之后,在detailed display中就会有此协议的标识,且选中它将会高亮此packet的剩余内容。如下图所示:

    现在,让我们进行下一步,添加一些协议解析。这一步我们需要创建2个表来帮助解析。这需要在proto_register_foo()函数中添加一些代码。

    在proto_register_foo()的前面添加了2个static数组。这些数组在proto_register_protocol()调用之后被注册。

    代码9.5 注册数据结构

    void
    proto_register_foo(void)
    {
        static hf_register_info hf[] = {
            { &hf_foo_pdu_type,
                { "FOO PDU Type", "foo.type",
                FT_UINT8, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL }
            }
        };
    
        /* Setup protocol subtree array */
        static gint *ett[] = {
            &ett_foo
        };
    
        proto_foo = proto_register_protocol (
            "FOO Protocol", /* name       */
            "FOO",      /* short name */
            "foo"       /* abbrev     */
            );
    
        proto_register_field_array(proto_foo, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
    }
    

    变量hf_foo_pdu_type和ett_foo也需要在此文件的前面声明。

    代码9.6 解析器数据结构全局变量

    static int hf_foo_pdu_type = -1;
    
    static gint ett_foo = -1;
    

    现在我们可以用一些细节来增加协议的显示。

    代码9.7 解析器开始解析packets

    if (tree) { /* we are being asked for details */
      proto_item *ti = NULL;
      proto_tree *foo_tree = NULL;
    
      ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
      foo_tree = proto_item_add_subtree(ti, ett_foo);
      proto_tree_add_item(foo_tree, hf_foo_pdu_type, tvb, 0, 1, ENC_BIG_ENDIAN);
    }
    

    现在解析开始看起来更加有趣了。我们开始破解此协议的第1个比特。packet起始处的一个字节数据定义了foo协议的packet type。

    proto_item_add_subtree()调用往协议树中增加了一个子节点。此节点的展开是由ett_foo变量控制的。它会记住节点是否应该展开,在你在packet中移动的时候。所有后续的解析会添加到此树中,就像在接下来的调用中看到的那样。proto_tree_add_item向foo_tree添加了新项,并用hf_foo_pdu_type来控制此项的格式。pdu type是1个字节的数据,从0开始。我们假定它是网络字节序(也叫big endian),因此用ENC_BGIG_ENDIAN。对于1个字节的数来说,没用字节序之说,但这是好的编程实践。

    我们来看static数组中的定义细节:

    • hf_foo_pdu_type - 此节点的索引
    • FOO PDU Type - 此项的标识
    • foo.type - 过滤用的字符串。它使我们可以在过滤器框中输入foo.type=1的语句
    • FT_UINT8 - 指出此项是一个8bit的无符号整数。
    • BASE_DEC - 对于整型来说,它令其打印为一个10进制数。还可以是16进制(BASE_HEX)或8进制(BASE_OCT)。

    我们目前忽略结构中的其余成员。

    如果此时编译并安装此插件,我们会看到它开始显示一些看起来有用的东西。

    现在我们来完成这个简单协议的解析。我们需要添加更多的变量在hf数组中,以及更多的函数调用。

    代码9.8 完成packet解析

    ...
    static int hf_foo_flags = -1;
    static int hf_foo_sequenceno = -1;
    static int hf_foo_initialip = -1;
    ...
    
    static void
    dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        gint offset = 0;
    
        ...
    
        if (tree) { /* we are being asked for details */
            proto_item *ti = NULL;
            proto_tree *foo_tree = NULL;
    
            ti = proto_tree_add_item(tree, proto_foo, tvb, 0, -1, ENC_NA);
            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);
            offset += 1;
            proto_tree_add_item(foo_tree, hf_foo_sequenceno, tvb, offset, 2, ENC_BIG_ENDIAN);
            offset += 2;
            proto_tree_add_item(foo_tree, hf_foo_initialip, tvb, offset, 4, ENC_BIG_ENDIAN);
            offset += 4;
        }
        ...
    }
    
    void
    proto_register_foo(void) {
        ...
            ...
            { &hf_foo_flags,
                { "FOO PDU Flags", "foo.flags",
                FT_UINT8, BASE_HEX,
                NULL, 0x0,
                NULL, HFILL }
            },
            { &hf_foo_sequenceno,
                { "FOO PDU Sequence Number", "foo.seqn",
                FT_UINT16, BASE_DEC,
                NULL, 0x0,
                NULL, HFILL }
            },
            { &hf_foo_initialip,
                { "FOO PDU Initial IP", "foo.initialip",
                FT_IPv4, BASE_NONE,
                NULL, 0x0,
                NULL, HFILL }
            },
            ...
        ...
    }
    ...
    

    这会解析这个假想协议的所有比特。We've introduced a new variable offset into the mix to help keep track of where we are in the packet dissection. With these extra bits in place, the whole protocol is now dissected.

    此时我们测试用的UDP发送端也要做些修改:

    //...
    struct proto_foo
    {
        UINT8  type;
        UINT8  flags;
        UINT16 seqno;
        UINT32 ipaddr;
    };
    
    //...
    int main(int argc, char** argv)
    {
    //...
        proto_foo data;
        data.ipaddr = inet_addr("192.168.1.2");
        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);
        }
    
    //...
    }
    

    此时的解析效果如下图所示:

    改进解析信息

    我们可以通过一些额外数据来改进协议的显示。第一步是添加一些文本标识。让我们从标识packet types开始。首先添加一个简单的type-name表。

    代码9.9 命名packet types

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

    接下来用VALS宏来把上表与数据的相应部分关联起来

    代码9.10 把名字添加到协议

    { &hf_foo_pdu_type,
        { "FOO PDU Type", "foo.type",
        FT_UINT8, BASE_DEC,
        VALS(packettypenames), 0x0,
        NULL, HFILL }
    }

    这有助于破解packets,我们可以对flags结构也这么做。

    代码9.11 把flags添加到协议

    #define FOO_START_FLAG 0x01
    #define FOO_END_FLAG        0x02
    #define FOO_PRIORITY_FLAG   0x04
    
    static int hf_foo_startflag = -1;
    static int hf_foo_endflag = -1;
    static int hf_foo_priorityflag = -1;
    
    static void
    dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        ...
            ...
            proto_tree_add_item(foo_tree, hf_foo_flags, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(foo_tree, hf_foo_startflag, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(foo_tree, hf_foo_endflag, tvb, offset, 1, ENC_BIG_ENDIAN);
            proto_tree_add_item(foo_tree, hf_foo_priorityflag, tvb, offset, 1, ENC_BIG_ENDIAN);
            offset += 1;
            ...
        ...
    }
    
    void
    proto_register_foo(void) {
        ...
            ...
            { &hf_foo_startflag,
                { "FOO PDU Start Flags", "foo.flags.start",
                FT_BOOLEAN, 8,
                NULL, FOO_START_FLAG,
                NULL, HFILL }
            },
            { &hf_foo_endflag,
                { "FOO PDU End Flags", "foo.flags.end",
                FT_BOOLEAN, 8,
                NULL, FOO_END_FLAG,
                NULL, HFILL }
            },
            { &hf_foo_priorityflag,
                { "FOO PDU Priority Flags", "foo.flags.priority",
                FT_BOOLEAN, 8,
                NULL, FOO_PRIORITY_FLAG,
                NULL, HFILL }
            },
            ...
        ...
    }
    ...
    

    这里有些东西要注意。对flags来说,因为每一个bit都是不同的flag,我们使用FT_BOOLEAN类型,因为flag不是开就是关。第二,we include the flag mask in the 7th field of the data, which allows the system to mask the relevant bit. We've also changed the 5th field to 8, to indicate that we are looking at an 8 bit quantity when the flags are extracted. Then finally we add the extra constructs to the dissection routine. Note we keep the same offset for each of the flags.

    此时的解析效果:

    This is starting to look fairly full featured now, but there are a couple of other things we can do to make things look even more pretty. At the moment our dissection shows the packets as "Foo Protocol" which whilst correct is a little uninformative. We can enhance this by adding a little more detail. First, let's get hold of the actual value of the protocol type. We can use the handy function tvb_get_guint8() to do this. With this value in hand, there are a couple of things we can do. First we can set the INFO column of the non-detailed view to show what sort of PDU it is - which is extremely helpful when looking at protocol traces. Second, we can also display this information in the dissection window.

    代码9.12 增强显示

    static void
    dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {
        guint8 packet_type = tvb_get_guint8(tvb, 0);
    
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        /* Clear out stuff in the info column */
        col_clear(pinfo->cinfo,COL_INFO);
        col_add_fstr(pinfo->cinfo, COL_INFO, "Type %s",
                 val_to_str(packet_type, packettypenames, "Unknown (0x%02x)"));
    
        if (tree) { /* we are being asked for details */
            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, packettypenames, "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;
        }
    }
    

    此时显示效果:

    So here, after grabbing the value of the first 8 bits, we use it with one of the built-in utility routines val_to_str(), to lookup the value. If the value isn't found we provide a fallback which just prints the value in hex. We use this twice, once in the INFO field of the columns - if it's displayed, and similarly we append this data to the base of our dissecting tree.

    最终源代码:

    #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;
    
    static void
    dissect_foo(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
    {   
        guint8 packet_type = tvb_get_guint8(tvb, 0);
        
        col_set_str(pinfo->cinfo, COL_PROTOCOL, "FOO");
        /* Clear out stuff in the info column */
        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)"));
        
        /* proto details display */
        if(tree)
        {
            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;
        }
    }
    
    
    void
    proto_register_foo(void)
    {
        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
                }
            }
        };
        
        static gint *ett[] = { &ett_foo };
    
        proto_foo = proto_register_protocol (
            "FOO Protocol", /* name       */
            "FOO",      /* short name */
            "foo"       /* abbrev     */
            );
            
        proto_register_field_array(proto_foo, hf, array_length(hf));
        proto_register_subtree_array(ett, array_length(ett));
    }
    
    void
    proto_reg_handoff_foo(void)
    {
        static dissector_handle_t foo_handle;
    
        foo_handle = create_dissector_handle(dissect_foo, proto_foo);
        dissector_add_uint("udp.port", FOO_PORT, foo_handle);
    }

     

    展开全文
  • 最近在用Wireshark抓包工具的时候,老感觉这东西用起来很简单,功能强大,所以想了解他的实现原理,我就自己好奇写了一个实现基本功能的demo吧。 其实叫抓包工具,其实就是抓取流经自己网卡的所有ip包,我们能够...
  • 长话短说,wireshark有一个follow tcp stream功能,这个功能很方便。美中不足的是提取出的stream 数据没有时间戳等其他信息,在分析数据的延时和...原理很简单,仍然是基于wireshark,里面有一个Export packet dissect

    长话短说,wireshark有一个follow tcp stream功能,这个功能很方便。美中不足的是提取出的stream 数据没有时间戳等其他信息,在分析数据的延时和丢包问题时就有些力不从心了。这里简单用python实现了一个简单follow tcp stream功能,同时保留了tcp信息。


    原理很简单,仍然是基于wireshark,里面有一个Export packet dissection as XML ‘pdml’ file。 导出来之后的文件内容是这个样子的:

    <proto name="tcp" showname="Transmission Control Protocol, Src Port: 59203 (59203), Dst Port: 80 (80), Seq: 1, Ack: 1, Len: 381" size="20" pos="34">
        <field name="tcp.srcport" showname="Source Port: 59203 (59203)" size="2" pos="34" show="59203" value="e743"/>
        <field name="tcp.dstport" showname="Destination Port: 80 (80)" size="2" pos="36" show="80" value="0050"/>
        <field name="tcp.port" showname="Source or Destination Port: 59203" hide="yes" size="2" pos="34" show="59203" value="e743"/>
        <field name="tcp.port" showname="Source or Destination Port: 80" hide="yes" size="2" pos="36" show="80" value="0050"/>
        <field name="tcp.stream" showname="Stream index: 4" size="0" pos="34" show="4"/>
        <field name="tcp.len" showname="TCP Segment Len: 381" size="1" pos="46" show="381" value="50"/>
        <field name="tcp.seq" showname="Sequence number: 1    (relative sequence number)" size="4" pos="38" show="1" value="3b0ac4bd"/>
        <field name="tcp.nxtseq" showname="Next sequence number: 382    (relative sequence number)" size="0" pos="34" show="382"/>
        <field name="tcp.ack" showname="Acknowledgment number: 1    (relative ack number)" size="4" pos="42" show="1" value="397d7582"/>
        <field name="tcp.hdr_len" showname="Header Length: 20 bytes" size="1" pos="46" show="20" value="50"/>


    看到上面的内容,我想什么都不用说了。用python简单的做个xml文件解析,将数据提取出来就可以了。

    那么剩下的一个问题就是follow tcp stream 这个算法如何实现?本质就是一个tcp数据如何重组的过程,具体可以参考这篇博文TCP数据包重组实现分析

    这里,简单起见,我做了一些约束:

    1.  只能提取A-->B这样的单个方向的数据。如果需要提取B-->A的数据,可以重新过滤一下数据,然后执行一次脚本。
    2. 忽略最开头的syn包和断开连接时的Fin包。
    基于上面两个简化,实际算法可以简化为根据tcp帧中的seq,从小到大排序。简单举个例子:有三个tcp包,按seq排序后如下样子

    (seq=1, nxtseq=5, data='1234') , (seq=4, nxt=6, data='45') , (seq=7,nxt=8, data='7')

    第一个数据包的nxtseq > 第二个数据的seq,说明两个数据包之间有数据重复,事实也是如此,重复了数字‘4’

    第二个数据包的nxtseq < 第三个数据包的seq,说明这两个数据包之间有丢帧。事实也是如此,丢失了数字‘6’

    好了,原理就介绍到这儿。


    剩下稍微介绍一下wireshark过滤规则和这个算法的局限性

    1. 按照ip过滤某个方向的数据,一般可以使先执行wireshark的follow tcp stream功能,一般在filter一栏会有这样一个表达式 tcp.stream eq xxx。在这个表达式的后面可以继续跟上ip过滤的表达式:  tcp.steam eq xxx and ip.src==xxx  and ip.dst==xxx
    2. 按照tcp端口号过滤某个方向的数据。首先同ip过滤,先固定到一条tcp连接上,得到tcp.stream eq xxx。 然后加上端口过滤:tcp.stream eq xxx and tcp.srcport==xxx and tcp.dstport==xxx
    3. 这个工具的局限性,因为是基于python element tree 对xml文件进行解析和数据提取,所以即使是解析100M的pcap文件,首先生成的pdml文件就会暴增到几百兆,然后这几百兆的文件又要被读入内存,(python element tree的特点),总计下来就是生成pdml文件有点慢(几分钟),内存消耗特大,几百兆。


    最后简单贴一些关键代码。完整的脚本可以从这里免费下载 TCPParser -- follow tcp stream by python

    这是从pdml文件的某个proto中提取需要的元素信息。

    def extract_element(self, proto, elem):
            result = dict()
            for key in elem.keys():
                result[key]=""
            
            fieldname   = ""
            attribname  = ""    
            for field in proto.findall("field"):
                fieldname = field.get('name')
                if fieldname in elem:
                    attribname = elem[fieldname]
                    result[fieldname] = field.get(attribname, '')
                    
            return result


    def regularize_stream(self, frame_list):
            '''
                        正则化tcp stream的数据,主要是根据seq,nxtseq补上缺少的segment,以及删除重复的数据
                        不少缺少的segment时,data为空,frame.number='lost'
                        删除重复的数据时,尽可能保留较早之前收到的数据,也就是前一个包的数据
            '''
            self.reporter.title("TCPParser regularize timestamp")
            timer = Timer("regularize_stream_data").start()
            reg_frame_list = []
            expectseq = -1
            first = True
            for frame in frame_list:
                if first:
                    # 第一个数据包
                    first = False
                    expectseq = frame["tcp.nxtseq"]
                    reg_frame_list.append(frame)
                    continue
                
                # 从第二个数据包开始
                seq = frame["tcp.seq"]
                nxtseq = frame["tcp.nxtseq"]
                if seq == expectseq :
                    # 数据刚好,完全连续,不多不少
                    if nxtseq == 0: continue # 表示ack包,无意义
                    expectseq = nxtseq
                    reg_frame_list.append(frame)
                elif seq > expectseq:
                    # 数据有缺失,说明丢包了
                    self.reporter.error("previous tcp segment is lost: " + str(frame[TCPFrame.KEY_FRAMENo]))
    #                newpacket = self.new_lost_packet(frame, str(expectseq), str(seq))
    #                reg_frame_list.append(newpacket)
                    reg_frame_list.append(frame)
                    expectseq = nxtseq
                    
                elif seq < expectseq:
                    # 数据有重叠,数据重传时补传过多数据了
                    self.reporter.warning("tcp segment retransmission: " + str(frame[TCPFrame.KEY_FRAMENo]))
                    if expectseq < nxtseq:
                        # 当前数据包需要舍弃一部分内容
                        # pre_packet[-(expectseq-seq):-1] == frame[0:expectseq-seq]
                        frame["tcp.seq"] = expectseq 
                        frame["data"] = frame["data"][expectseq-nxtseq:]
                        frame["datalen"] = len(frame["data"])
                        expectseq = nxtseq
                        reg_frame_list.append(frame)
                    else:
                        # 当前数据包的内容可以完全舍弃
                        # expectseq 保持不变
                        # pre_packet[-(nxtseq-seq):] = frame[:nextseq-seq]
                        pass
            timer.stop()        
            return reg_frame_list



    展开全文
  • 目录   内容 步骤 (一)、FTP原理 ...(六)、Wireshark抓FTP流量包分析 (附录)FTP的C语言代码实现 内容 1、FTP原理 2、FTP工作方式 3、FTP传输模式 4、iis搭建ftp服务器(fil...
  • 正常下班,文章走起。...本篇文章通过使用wireshark对网络通信扑捉,进行原理分析。1BIO代码实现//服务端代码publicclassServerSocket{publicstaticvoidmain(String[]args)throwsException{//创建Ser...
  • wireshark过滤语法

    2018-04-26 19:35:40
    原理 网络原理: 在交换机环境中实现抓取网段中所有包的手段: ...端口镜像:通过昂贵的路由设备,将其他端口的数据拷贝到目标端口,实现引流 arp欺骗:在主机进行arp广播的时候,对网关进行arp...实现原理: ...
  • 本篇文章通过使用wireshark对网络通信扑捉,进行原理分析。 1 BIO代码实现 //服务端代码 publicclassServerSocket{ publicstaticvoidmain(String[]args)throwsException{ //创建ServerSocket对象,用于...
  • SSL工作原理介绍以及java实现

    万次阅读 多人点赞 2017-01-17 11:21:15
    SSL工作原理介绍以及java实现目录SSL工作原理介绍以及java实现 SSL简介 SSL工作原理 握手协议Handshake protocol 1握手阶段使用RSA加密算法 2握手阶段使用Diffie-Hellman加密算法 记录协议Record protocol 警报协议...
  • 实现过程: 1.配置系统环境变量 变量名:SSLKEYLOGFILE 变量值:随意指定一个存储路径,以便chrome输出keylog 2.配置Wireshark 填入你在系统变量中指定的keylog存储路径,以便wireshark访问keylog中的key ...
  • wireshark实验五:UDP

    千次阅读 2018-08-16 22:57:23
    二、实现原理 UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务...
  • Wireshark嗅探和协议分析

    千次阅读 2018-11-29 21:21:21
    2、通过对Wireshark抓包实例进行分析,实现捕捉DHCP、ARP、FTP、HTTP等协议的数据包;理解TCP/IP协议中多种协议的数据结构、会话连接建立和终止的过程;了解FTP、HTTP等协议明文传输特性,增强安全意识。 二、实验...
  • 基本原理:2.1 机密性保证2.2 消息完整性保证2.3 身份验证3.SSL\TLS握手:3.1 参数协商3.2 身份验证3.3 密钥交换最终生成的秘钥一共有4个,分别是:4.握手阶段示意图:二、使用openssl生成自签名证书1.制作CA2.签发...
  • 本篇文章通过使用wireshark对网络通信扑捉,进行原理分析。1BIO代码实现//服务端代码 public class ServerSocket { public static void main(String[] args) throws Exception { //创建Serve...
  • 使用Wireshark与Burp Suite分析HTTPS协议

    千次阅读 2018-12-13 14:10:36
    2.Wireshark和Burp Suite处理HTTPS的过程与技术实现过程原理 (1)Wireshark (2)Burp Suite 3.在QT中编写HTTP程序并运行。 代码 目的 了解HTTPS是如何加密,SSL加密过程。 内容 1...
  • SIP 是VOIP目前非常流行的一种协议。有关协议的详细原理参照相关文档。    本文通过wireshark抓包分析SIP ... 根据SIP协议原理,SIP终端,也就是本文中的数字家庭产品,实现了UAC(User Agent Client)和UAS(User
  • 利用wireshark分析ARP数据包3.ARP攻击二、kali实现ARP攻击 一、ARP协议和ARP攻击 1.ARP协议 ARP协议,地址解析协议(Address Resolution Protocol),用来实现IP地址到物理地址的映射。 每一台主机都设有一个ARP高速...
  • 微信域名防封主要是通过技术手段来实现预防措施,网络上的什么不死域名完全是无稽之谈,没有哪家是可以做到完全防封的。...下面就简单介绍一下微信域名防封检测的技术原理实现方式 原理 使用 Wireshark
  • 微信域名防封主要是通过技术手段来实现预防措施,网络上的什么不死域名完全是无稽之谈,没有哪家是可以做到完全防封的。...下面就简单介绍一下微信域名防封检测的技术原理实现方式 原理 使用 Wireshark 抓包获取微

空空如也

空空如也

1 2 3 4 5 6
收藏数 112
精华内容 44
关键字:

wireshark实现原理