计算机网络课程设计之网络嗅探器的设计与实现

前言

本实验难点是在于Windows下的raw socket有太多的限制,因此用winpcap编程功能更加强大,但是根据指导书要求要用原始套接字,原始套接字在Linux系统下也十分强大,结尾附上Linuxraw socket源码 ,但是因为我本人想要将课设全部写在同一个软件中,只能查询资料实现Windows。
参考博客 :(感谢大佬)
https://www.writebug.com/git/codes?owner=Schoolleave&repo=RawSocket_Test
不过需要指出的是,大佬的代码对于TCP的解析是有问题的,第一是TCP报文长度,第二个是TCP数据部分的解析,由于课设时间和本人的能力有限,仅做了部分修改。再次感谢大佬的开源!!!

白嫖容易,创作不易,本文原创,转载请注明!!!
源码和可运行程序:
链接:https://pan.baidu.com/s/1A9KctmpP2JJgyW2wLrehIg
提取码:Lin2

计算机网络课程设计:
计算机网络课程设计之网络聊天程序的设计与实现
计算机网络课程设计之Tracert与Ping程序设计与实现
计算机网络课程设计之基于 IP 多播的网络会议程序
计算机网络课程设计之网络嗅探器的设计与实现
计算机网络课程设计之电子邮件客户端程序设计与实现
计算机网络课程设计之TELNET 终端设计与实现
计算机网络课程设计之网络代理服务器的设计与实现
计算机网络课程设计之简单 Web Server 程序的设计与实现

Qt入门系列:
Qt学习之C++基础
Qt学习之Qt安装
Qt学习之Qt基础入门(上)
Qt学习之Qt基础入门(中)
Qt学习之Qt基础入门(下)

创作不易,整个课程设计程序3000多行代码,所有实验都写在了一个程序中,时间有限,能力不足,转载望注明!!!
本文链接
个人博客:https://ronglin.fun/archives/270
PDF链接:见博客网站
CSDN: https://blog.csdn.net/RongLin02/article/details/122510398

实验题目

网络嗅探器的设计与实现

实验目的

参照附录 4 raw socket 编程例子,设计一个可以监视网络的状态、数据流动情况以及网络上传输的信息的网络嗅探器。

总体设计

(含背景知识或基本原理与算法、或模块介绍、设计步骤等)
主要用到的是RawSocket
主要是思路是:
创建RawSocket套接字-> 绑定到对应的网卡上 -> 设置混杂模式(为了抓到所有包) -> 调用recvfrom()接受数据 ->解析数据
主要的难点是要根据抓到的数据报解析协议,然后根据不同的协议解析对应的数据,注意的是要根据不用的协议的不同的首部长度来提取数据内容
背景知识

  1. 原始套接字工作原理与规则
    原始套接字是一种不同于 SOCK_STREAM 和 SOCK_DGRAM 的套接字,它实现于系统核心。它的创建方式跟 TCP/UDP 创建方法几乎是一模一样,例如,通过
int sockfd;
sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);

这两句程序你就可以创建一个原始套接字。这种类型套接字的功能与 TCP 或者 UDP 类型套接字的功能有很大的不同:TCP/UDP 类型的套接字只能够访问传输层以及传输层以上的数据,因为当 IP层把数据传递给传输层时,下层的数据包头已经被丢掉了。而原始套接字却可以访问传输层以下的数据,所以使用 raw 套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作。比如:通过

sock=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP))

方式创建的 rawsocket 就能直接读取链路层的数据。
1)使用原始套接字时应该注意的问题(参考<<unix 网络编程>>以及网上的优秀文档)
(1):对于 UDP/TCP 产生的 IP 数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的 UDP/TCP 数据处理句柄(所以,如果你想要通过原始套接字来访问 TCP/UDP 或者其它类型的数据,调用 socket 函数创建原始套接字第三个参数应该指定为 htons(ETH_P_IP),也就是通过直接访问数据链路层来实现。(我们后面的密码窃取器就是基于这种类型的)。
(2):对于 ICMP 和 EGP 等使用 IP 数据包承载数据但又在传输层之下的协议类型的 IP 数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些 IP 数据包复制一份传递给协议类型匹配的原始套接字。
(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些 IP 数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个 IP 数据包,并返回一个 ICMP 主机不可达的消息给源主机。
(4):如果原始套接字 bind 绑定了一个地址,核心只将目的地址为本机 IP 地址的数据包传递给原始套接字,如果某个原始套接字没有 bind 地址,核心就会把收到的所有 IP 数据包发给这个原始套接字。
(5):如果原始套接字调用了 connect 函数,则核心只将源地址为 connect 连接的 IP 地址的 IP 数据包传递给这个原始套接字。
(6):如果原始套接字没有调用 bind 和 connect 函数,则核心会将所有协议匹配的 IP 数据包传递给这个原始套接字。
2. 编程选项
原始套接字是直接使用 IP 协议的非面向连接的套接字,在这个套接字上可以调用 bind 和 connect函数进行地址绑定。说明如下:
(1)bind 函数:调用 bind 函数后,发送数据包的源 IP 地址将是 bind 函数指定的地址。如果不调用bind,则内核将以发送接口的主IP地址填充IP头 。 如 果 使 用 setsockopt 设置了IP_HDRINCL(headerincluding)选项,就必须手工填充每个要发送的数据包的源 IP 地址,否则,内核将
自动创建 IP 首部。
(2)connetc 函数:调用 connect 函数后,就可以使用 write 和 send 函数来发送数据包,而且内核将会用这个绑定的地址填充 IP 数据包的目的 IP 地址,否则的话,则应使用 sendto 或 sendmsg 函数来发送数据包,并且要在函数参数中指定对方的 IP 地址。
综合以上种种功能和特点,我们可以使用原始套接字来实现很多功能,比如最基本的数据包分析,主机嗅探等。其实也可以使用原始套接字作一个自定义的传输层协议

详细设计

(含主要的数据结构、程序流程图、关键代码等)
核心的功能方法主要有两个
1.转化结果

QString getDataFromPacket(BYTE *lpBuf, int iLen, int iPrintType);

此方法是用来将一个字节数组转化为一个可输出的字符串,int iPrintType有两个参数,一个是按照16进制输出,还有一个是按照ASCII码输出。
前者用到的主要解析代码如下

len += sprintf(t+len,"%02x ",lpBuf[i]);

主要是用系统的格式化输出到字符串中,用到%x输出
后者解析ASCII码用的是强制类型转化,如下

t[len++] =(char)lpBuf[i];
  1. 解析数据报
void analyseRecvPacket(BYTE *lpBuf);

这个方法是核心,当然主要是参考了大佬的解析代码,下边进行简单的说明
主要流程有两个,一个是先分析IP包的协议类型,第二个是根据协议类型分析首部和数据部分,代码如下:

void MySniffer::analyseRecvPacket(BYTE *lpBuf)
{
    //Windows上没办法用Raw Socket抓MAC层的数据包,只能抓到IP层及以上的数据包!!!
    //注意:数据包的字节顺序转换问题!!!
    //这里要将网络字节序转换为本地字节序
    struct sockaddr_in saddr, daddr;
    PIPV4HEADER t_ip = (PIPV4HEADER)lpBuf;
    if(t_ip->ipv4_pro == IPPROTO_ICMP)
    {
        saddr.sin_addr.s_addr = t_ip->ipv4_sourpa;
        daddr.sin_addr.s_addr = t_ip->ipv4_destpa;
        emit this->sendData(QStringList()<<"ICMP"<<inet_ntoa(saddr.sin_addr)<<inet_ntoa(daddr.sin_addr)<<""<<"");
    }
    else if(t_ip->ipv4_pro == IPPROTO_IGMP)
    {
        saddr.sin_addr.s_addr = t_ip->ipv4_sourpa;
        daddr.sin_addr.s_addr = t_ip->ipv4_destpa;
        emit this->sendData(QStringList()<<"IGMP"<<inet_ntoa(saddr.sin_addr)<<inet_ntoa(daddr.sin_addr)<<""<<"");
    }
    else if(t_ip->ipv4_pro == IPPROTO_TCP)
    {
        PTCPHEADER tcp = (PTCPHEADER)(lpBuf + (t_ip->ipv4_ver_hl & 0x0F) * 4);
        int hlen = ((t_ip->ipv4_ver_hl & 0x0F) * 4) + ((((tcp->tcp_hlen)>>4 & 0x0F) * 4)& 0x0F);
        int dlen = ((quint16)ntohs(t_ip->ipv4_plen )) - hlen;    //这里要将网络字节序转换为本地字节序
        saddr.sin_addr.s_addr = t_ip->ipv4_sourpa;
        daddr.sin_addr.s_addr = t_ip->ipv4_destpa;
        QStringList string_tcp;
        string_tcp<<"TCP";
        string_tcp <<QString(inet_ntoa(saddr.sin_addr))+":"+QString::number(ntohs(tcp->tcp_sourport));
        string_tcp <<QString(inet_ntoa(daddr.sin_addr))+":"+QString::number(ntohs(tcp->tcp_destport));
        string_tcp<<getDataFromPacket((lpBuf + hlen), dlen, 0);
        //MyPrintf("ack:%u  syn:%u length=%d\n", tcp->tcp_acknu, tcp->tcp_seqnu, dlen);
        string_tcp <<QString("ack:%1  syn:%2 length=%3").arg(tcp->tcp_acknu).arg(tcp->tcp_seqnu).arg(dlen);
        emit this->sendData(string_tcp);
    }
    else if(t_ip->ipv4_pro == IPPROTO_UDP)
    {
        PUDPHEADER udp = (PUDPHEADER)(lpBuf + (t_ip->ipv4_ver_hl & 0x0F) * 4);
        int hlen = (int)((t_ip->ipv4_ver_hl & 0x0F) * 4 + sizeof(UDPHEADER));
        int dlen = (int)(ntohs(udp->udp_hlen) - 8);
        //	int dlen = (int)(udp->udp_hlen - 8);
        saddr.sin_addr.s_addr = t_ip->ipv4_sourpa;
        daddr.sin_addr.s_addr = t_ip->ipv4_destpa;
        QStringList string_udp;
        string_udp<<"UDP";
        string_udp <<QString(inet_ntoa(saddr.sin_addr))+":"+QString::number(ntohs(udp->udp_sourport));
        string_udp <<QString(inet_ntoa(daddr.sin_addr))+":"+QString::number(ntohs(udp->udp_destport));

        string_udp<<getDataFromPacket((lpBuf + hlen), dlen, 0);
        string_udp <<"";
        emit this->sendData(string_udp);
    }
    else
    {
        saddr.sin_addr.s_addr = t_ip->ipv4_sourpa;
        daddr.sin_addr.s_addr = t_ip->ipv4_destpa;
        emit this->sendData(QStringList()<<"OTHER"<<inet_ntoa(saddr.sin_addr)<<inet_ntoa(daddr.sin_addr)<<""<<"");
    }

}

需要注意的是,数据报的格式定义在了一个packetstruct.h的文件中,其中定义了IPV4、IPV6、ARP字段结构体、以太网帧头格式结构体、TCP、UDP的结构体

实验结果与分析

在这里插入图片描述
点击保存到文件,然后把数据保存到,软件运行路径下
在这里插入图片描述
打开txt文件,可以看到数据格式
在这里插入图片描述
可以注意到,TCP格式的解析有问题,UDP的首部是固定8字节,解析起来就比较简单,所以没啥问题,但是TCP首部复杂,问题主要出现在长度解析和内容解析,精力有限,如果还有时间就再次深入的修改一下代码。
根据TCP首部,做了部分修改

小结与心得体会

通常我们都是使用类似WireShark的抓包软件嗅探数据包,这些抓包工具大部分都是基于WinPcap库实现的数据包嗅探功能。而现在,本实验是使用原始套接字的RawSocket方式实现数据包的嗅探。当然,和WinPcap相比,Raw Socket有很大的局限性。,它只能抓到IP层及以上的数据包,抓不到MAC层的数据包。而且还需要管理员权限,但是本次实验对于TCP/UDP的数据格式有了很深的理解,获益匪浅。
=w=

猜你喜欢

转载自blog.csdn.net/RongLin02/article/details/122510398