赞
踩
本实验难点是在于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()
接受数据 ->解析数据
主要的难点是要根据抓到的数据报解析协议,然后根据不同的协议解析对应的数据,注意的是要根据不用的协议的不同的首部长度来提取数据内容
背景知识
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];
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=
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。