zoukankan      html  css  js  c++  java
  • MFC+WinPcap编写一个嗅探器之七(协议)

    这一节是本系列教程的结尾了,内容也比较简单,主要是对网络协议进行分析,其实学过计算机网络的同学完全可以略过

    在整个项目中需要有一个头文件存放各层协议的头部定义,我把它们放在了head.h中,这个头文件都有什么呢,首先放几个关于协议的宏定义,这样可以让整个程序显得更加清晰:

     1 /* 网络层协议类型 */
     2 #define IP       0x0800          
     3 #define ARP      0x0806          
     4 #define RARP     0x8035 
     5 
     6 /* 传输层类型 */
     7 #define ICMP       0x01
     8 #define IGMP       0x02 
     9 #define TCP        0x06
    10 #define EGP        0x08   
    11 #define UDP        0x11 
    12 #define IPv6       0x29
    13 #define OSPF       0x59

     之后就是存放协议的头部定义了,对照着头部定义,用合适的类型定义各个字段,比如IP协议头:

     1 typedef struct ip_address
     2 {
     3     u_char byte1;
     4     u_char byte2;
     5     u_char byte3;
     6     u_char byte4;
     7 }ip_address;
     8 
     9 /* IPv4 首部 */
    10 typedef struct ip_header
    11 {
    12     u_char  ver_ihl;        // 版本 (4 bits) + 首部长度 (4 bits)
    13     u_char  tos;            // 服务类型(Type of service) 
    14     u_short tlen;           // 总长(Total length) 
    15     u_short identification; // 标识(Identification)
    16     u_short flags_fo;       // 标志位(Flags) (3 bits) + 段偏移量(Fragment offset) (13 bits)
    17     u_char  ttl;            // 生存时间(Time to live)
    18     u_char  type;           // 协议(Protocol)
    19     u_short crc;            // 首部校验和(Header checksum)
    20     ip_address  saddr;      // 源地址(Source address)
    21     ip_address  daddr;      // 目的地址(Destination address)
    22     u_int   op_pad;         // 选项与填充(Option + Padding)
    23 }ip_header;

     当然有些位置的意义是小于一个字节大小的,比如TCP协议中的标志位字段,有URG,ACK,PSH,RST,SYN,FIN,这些在之后用&&运算可以方便求得

    当数据包被pcap_next_ex()函数捕获后,我们需要其中的两个参数,pcap_next_ex()函数原型如下:

    1 int     pcap_next_ex(pcap_t *, struct pcap_pkthdr **, const u_char **);

    第一个参数是一个已打开的捕捉实例,也就是之前我们选择的适配器,第二个参数是一个结构体,其中内容有:

    1 struct pcap_pkthdr
    2 {
    3       struct timeval ts;   ts是一个结构struct timeval,它有两个部分,第一部分是1900开始以来的秒数,第二部分是当前秒之后的毫秒数
    4       bpf_u_int32 caplen;  表示抓到的数据长度
    5       bpf_u_int32 len;    表示数据包的实际长度
    6 }

    因为在某些情况下你不能保证捕获的包是完整的,例如一个包长1480,但是你捕获到1000的时候,可能因为某些原因就中止捕获了,所以caplen是记录实际捕获的包长,也就是1000,而len就是1480。第三个参数就是数据包的内容后两个参数是对我们有用的,将其传递到显示数据包概略的函数ShowPacketList中

    每当捕获以个数据包,需要进行保存,在MFC中可以用CArray,CArray是一个动态数组,它的用法是:CArray<Object,Object> Var1;前一个参数是指定存储在数组中的对象的类型,后一个参数是指定用于访问存储在数组中对象的参数类型。其实这两个参数一般是一样的类型,程序中存储每次抓包的pkt_header和pkt_data就用如下CArray表示:

    CArray<const struct pcap_pkthdr *,const struct pcap_pkthdr *>  m_pktHeaders;
    CArray<const u_char *,const u_char *>  m_pktDatas; 

    CArray类的一些常用成员函数有:GetAt返回在给定索引上的值;Add增加一个元素;GetSize获得此数组中的元素数 

    存储完捕获的数据包,就可以利用pkt_data的信息来分析数据包,在分析数据包中的数据时注意,数据包的数据是按照小端模式存储的,对于大于1字节的内容需要用noths或是nothl进行转化(前者对应short,后者对应Long)。举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:0x78 | 0x56 | 0x34 | 0x12 ,这时就需要noths来转化为正确的顺序。显示数据包概要的函数部分代码如下:

     1 ethernet_header *eh;
     2     eh = (ethernet_header *)pkt_data;
     3     str.Format(_T("%x:%x:%x:%x:%x:%x"),eh->saddr.byte1,eh->saddr.byte2,eh->saddr.byte3,eh->saddr.byte4,eh->saddr.byte5,eh->saddr.byte6);
     4     m_list1.SetItemText(nCount,2,str);
     5     str.Format(_T("%x:%x:%x:%x:%x:%x"),eh->daddr.byte1,eh->daddr.byte2,eh->daddr.byte3,eh->daddr.byte4,eh->daddr.byte5,eh->daddr.byte6);
     6     m_list1.SetItemText(nCount,3,str);
     7     str.Format(_T("%ld"),pHeader->len);
     8     m_list1.SetItemText(nCount,4,str);
     9     /*处理网络层*/
    10     switch(ntohs(eh->type))
    11     {
    12     case IP:
    13         {
    14             ip_header *ih;
    15             const u_char *ip_data;
    16             ip_data=pkt_data+14;
    17             ih = (ip_header *)ip_data;
    18             u_int ip_len;//IP首部长度
    19             ip_len = (ih->ver_ihl & 0xf) * 4;
    20             str.Format(_T("%d.%d.%d.%d"),ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4);
    21             m_list1.SetItemText(nCount,6,str);
    22             str.Format(_T("%d.%d.%d.%d"),ih->saddr.byte1,ih->saddr.byte2,ih->saddr.byte3,ih->saddr.byte4);
    23             m_list1.SetItemText(nCount,7,str);
    24             /*处理传输层*/
    25             switch(ih->type)
    26             {
    27             case TCP:

    就这样陷入一层一层的分析中,对于数据包的详细分析,也就是在树形结构中显示的代码也与次类似,不做分析。

    在统计部分,除了有统计各种数据包的数目外,还有一部分是流量分析:

    这一部分的实现也比较简单,对于某些常用的软件,都是通过固定端口进行数据传输的,比如QQ 默认采用 UDP 通讯方式,端口 8000,8001。如果 UDP 的两个端口不通,会自动转换到 TCP 80 端口或者 TCP 443 端口进行通讯。 QQ 同时也支持 HTTP 代理模式及 SOCK5代理模式。阿里旺旺采用 TCP 通讯方式,默认登录端口为 16000,当 16000 端口不通时,则跳转到 443 端口进行通讯。所以通过对固定端口的监听,可以知道哪些软件产生了流量(这样做并不完美,有兴趣的可以分析应用层协议)

    1     if(ntohs( th->sport ) == 0x3E80 || ntohs( th->dport ) == 0x3E80)    //阿里旺旺流量为TCP端口16000 
    2            m_wangCount++;
    3     if(ntohs( th->sport ) == 0x747 || ntohs( th->dport ) == 0x747)        //MSN流量为TCP端口1863 
    4            m_msnCount++;

    写在最后:

    整体感觉编写一个嗅探器的技术层面上并不困难,但是比较繁琐,尤其是在界面方面,但是对理解网络协议还是很有帮助的。如果您需要源代码可以点此下载。源码中有一些冗余,有些数据在传递过程中并不安全,有些变量为了方便放在了public中,还请酌情参考。

  • 相关阅读:
    在Ubuntu下安装Apache
    linux 安装jdk 配置tomcat
    linux命令——rmdir
    linux命令——rm
    WebApi路由解析增加版本控制
    eclipse Dynamic web module相关问题
    mysql 5.7.18 windows zip安装
    微信扫描二维码登录网站技术原理
    Maven实战(八)——常用Maven插件介绍(下)
    Maven实战(七)——常用Maven插件介绍(上)
  • 原文地址:https://www.cnblogs.com/raichen/p/4133631.html
Copyright © 2011-2022 走看看